aboutsummaryrefslogtreecommitdiff
path: root/kernel/drivers
diff options
context:
space:
mode:
authornthnluu <nate1299@me.com>2024-01-28 21:20:27 -0500
committernthnluu <nate1299@me.com>2024-01-28 21:20:27 -0500
commitc63f340d90800895f007de64b7d2d14624263331 (patch)
tree2c0849fa597dd6da831c8707b6f2603403778d7b /kernel/drivers
Created student weenix repository
Diffstat (limited to 'kernel/drivers')
-rw-r--r--kernel/drivers/Submodules1
-rw-r--r--kernel/drivers/blockdev.c96
-rw-r--r--kernel/drivers/chardev.c43
-rw-r--r--kernel/drivers/cmos.c78
-rw-r--r--kernel/drivers/disk/sata.c512
-rw-r--r--kernel/drivers/keyboard.c208
-rw-r--r--kernel/drivers/memdevs.c108
-rw-r--r--kernel/drivers/pcie.c77
-rw-r--r--kernel/drivers/screen.c513
-rw-r--r--kernel/drivers/tty/ldisc.c120
-rw-r--r--kernel/drivers/tty/tty.c135
-rw-r--r--kernel/drivers/tty/vterminal.c1384
12 files changed, 3275 insertions, 0 deletions
diff --git a/kernel/drivers/Submodules b/kernel/drivers/Submodules
new file mode 100644
index 0000000..dc26997
--- /dev/null
+++ b/kernel/drivers/Submodules
@@ -0,0 +1 @@
+tty disk
diff --git a/kernel/drivers/blockdev.c b/kernel/drivers/blockdev.c
new file mode 100644
index 0000000..5c8eb82
--- /dev/null
+++ b/kernel/drivers/blockdev.c
@@ -0,0 +1,96 @@
+#include "kernel.h"
+#include "util/debug.h"
+#include <drivers/disk/sata.h>
+
+#include "drivers/blockdev.h"
+
+#include "mm/pframe.h"
+#include "fs/s5fs/s5fs.h"
+
+#ifdef NO
+static mobj_ops_t blockdev_mobj_ops = {.get_pframe = NULL,
+ .fill_pframe = blockdev_fill_pframe,
+ .flush_pframe = blockdev_flush_pframe,
+ .destructor = NULL};
+#endif
+
+static list_t blockdevs = LIST_INITIALIZER(blockdevs);
+
+void blockdev_init() { sata_init(); }
+
+long blockdev_register(blockdev_t *dev)
+{
+ if (!dev || dev->bd_id == NULL_DEVID || !dev->bd_ops)
+ {
+ return -1;
+ }
+
+ list_iterate(&blockdevs, bd, blockdev_t, bd_link)
+ {
+ if (dev->bd_id == bd->bd_id)
+ {
+ return -1;
+ }
+ }
+
+#ifdef NO
+ mobj_init(&dev->bd_mobj, MOBJ_BLOCKDEV, &blockdev_mobj_ops);
+#endif
+
+ list_insert_tail(&blockdevs, &dev->bd_link);
+ return 0;
+}
+
+blockdev_t *blockdev_lookup(devid_t id)
+{
+ list_iterate(&blockdevs, bd, blockdev_t, bd_link)
+ {
+ if (id == bd->bd_id)
+ {
+ return bd;
+ }
+ }
+ return NULL;
+}
+
+#ifdef NO
+static long blockdev_fill_pframe(mobj_t *mobj, pframe_t *pf)
+{
+ KASSERT(mobj && pf);
+ KASSERT(pf->pf_pagenum <= (1UL << (8 * sizeof(blocknum_t))));
+ blockdev_t *bd = CONTAINER_OF(mobj, blockdev_t, bd_mobj);
+ return bd->bd_ops->read_block(bd, pf->pf_addr, (blocknum_t)pf->pf_pagenum,
+ 1);
+}
+
+static long blockdev_flush_pframe(mobj_t *mobj, pframe_t *pf)
+{
+ KASSERT(mobj && pf);
+ KASSERT(pf->pf_pagenum <= (1UL << (8 * sizeof(blocknum_t))));
+ dbg(DBG_S5FS, "writing disk block %lu\n", pf->pf_pagenum);
+ blockdev_t *bd = CONTAINER_OF(mobj, blockdev_t, bd_mobj);
+ return bd->bd_ops->write_block(bd, pf->pf_addr, (blocknum_t)pf->pf_pagenum,
+ 1);
+}
+#endif
+
+long blockdev_fill_pframe(mobj_t *mobj, pframe_t *pf)
+{
+ KASSERT(mobj && pf);
+ KASSERT(pf->pf_pagenum <= (1UL << (8 * sizeof(blocknum_t))));
+ blockdev_t *bd = CONTAINER_OF(mobj, s5fs_t, s5f_mobj)->s5f_bdev;
+ KASSERT(pf->pf_loc);
+ return bd->bd_ops->read_block(bd, pf->pf_addr, (blocknum_t)pf->pf_loc,
+ 1);
+}
+
+long blockdev_flush_pframe(mobj_t *mobj, pframe_t *pf)
+{
+ KASSERT(mobj && pf);
+ KASSERT(pf->pf_pagenum <= (1UL << (8 * sizeof(blocknum_t))));
+ dbg(DBG_S5FS, "writing disk block %lu\n", pf->pf_pagenum);
+ blockdev_t *bd = CONTAINER_OF(mobj, s5fs_t, s5f_mobj)->s5f_bdev;
+ KASSERT(pf->pf_loc);
+ return bd->bd_ops->write_block(bd, pf->pf_addr, (blocknum_t)pf->pf_loc,
+ 1);
+} \ No newline at end of file
diff --git a/kernel/drivers/chardev.c b/kernel/drivers/chardev.c
new file mode 100644
index 0000000..b8eb146
--- /dev/null
+++ b/kernel/drivers/chardev.c
@@ -0,0 +1,43 @@
+#include "drivers/chardev.h"
+#include "drivers/memdevs.h"
+#include "drivers/tty/tty.h"
+#include "kernel.h"
+#include "util/debug.h"
+
+static list_t chardevs = LIST_INITIALIZER(chardevs);
+
+void chardev_init()
+{
+ tty_init();
+ memdevs_init();
+}
+
+long chardev_register(chardev_t *dev)
+{
+ if (!dev || (NULL_DEVID == dev->cd_id) || !(dev->cd_ops))
+ {
+ return -1;
+ }
+ list_iterate(&chardevs, cd, chardev_t, cd_link)
+ {
+ if (dev->cd_id == cd->cd_id)
+ {
+ return -1;
+ }
+ }
+ list_insert_tail(&chardevs, &dev->cd_link);
+ return 0;
+}
+
+chardev_t *chardev_lookup(devid_t id)
+{
+ list_iterate(&chardevs, cd, chardev_t, cd_link)
+ {
+ KASSERT(NULL_DEVID != cd->cd_id);
+ if (id == cd->cd_id)
+ {
+ return cd;
+ }
+ }
+ return NULL;
+}
diff --git a/kernel/drivers/cmos.c b/kernel/drivers/cmos.c
new file mode 100644
index 0000000..5f6ed34
--- /dev/null
+++ b/kernel/drivers/cmos.c
@@ -0,0 +1,78 @@
+#include "drivers/cmos.h"
+
+int cmos_update_flag_set()
+{
+ outb(CMOS_ADDR, CMOS_REG_STAT_A);
+ return (inb(CMOS_DATA) & 0x80);
+}
+
+unsigned char cmos_read_register(int reg)
+{
+ outb(CMOS_ADDR, reg);
+ return inb(CMOS_DATA);
+}
+
+int rtc_time_match(rtc_time_t a, rtc_time_t b)
+{
+ return (a.second == b.second) && (a.minute == b.minute) &&
+ (a.hour == b.hour) && (a.day == b.day) && (a.month == b.month) &&
+ (a.year == b.year) && (a.__century == b.__century);
+}
+
+rtc_time_t __get_rtc_time()
+{
+ rtc_time_t tm;
+
+ while (cmos_update_flag_set())
+ ;
+
+ tm.second = cmos_read_register(CMOS_REG_SECOND);
+ tm.minute = cmos_read_register(CMOS_REG_MINUTE);
+ tm.hour = cmos_read_register(CMOS_REG_HOUR);
+ tm.day = cmos_read_register(CMOS_REG_DAY);
+ tm.month = cmos_read_register(CMOS_REG_MONTH);
+ tm.year = cmos_read_register(CMOS_REG_YEAR);
+ tm.__century = cmos_read_register(CMOS_REG_CENTURY);
+
+ return tm;
+}
+
+/* Our ticks -> time calculation is so suspect, we just get the time from the
+ * CMOS RTC */
+rtc_time_t rtc_get_time()
+{
+ // Check the result of CMOS twice to ensure we didn't get a torn read.
+ rtc_time_t tm_a;
+ rtc_time_t tm_b;
+
+ do
+ {
+ tm_a = __get_rtc_time();
+ tm_b = __get_rtc_time();
+ } while (!rtc_time_match(tm_a, tm_b));
+
+ unsigned char cmos_settings = cmos_read_register(CMOS_REG_STAT_B);
+
+ // Convert from BCD
+ if (!(cmos_settings & 0x04))
+ {
+ tm_a.second = (tm_a.second & 0x0F) + ((tm_a.second / 16) * 10);
+ tm_a.minute = (tm_a.minute & 0x0F) + ((tm_a.minute / 16) * 10);
+ tm_a.hour = ((tm_a.hour & 0x0F) + (((tm_a.hour & 0x70) / 16) * 10)) |
+ (tm_a.hour & 0x80);
+ tm_a.day = (tm_a.day & 0x0F) + ((tm_a.day / 16) * 10);
+ tm_a.month = (tm_a.month & 0x0F) + ((tm_a.month / 16) * 10);
+ tm_a.year = (tm_a.year & 0x0F) + ((tm_a.year / 16) * 10);
+ tm_a.__century = (tm_a.__century & 0x0F) + ((tm_a.__century / 16) * 10);
+ }
+
+ // Convert 12-hour clock to 24-hour clock:
+ if (!(cmos_settings & 0x02) && (tm_a.hour & 0x80))
+ {
+ tm_a.hour = ((tm_a.hour & 0x7F) + 12) % 24;
+ }
+
+ tm_a.year += (tm_a.__century * 100);
+
+ return tm_a;
+} \ No newline at end of file
diff --git a/kernel/drivers/disk/sata.c b/kernel/drivers/disk/sata.c
new file mode 100644
index 0000000..00ac63d
--- /dev/null
+++ b/kernel/drivers/disk/sata.c
@@ -0,0 +1,512 @@
+#include <drivers/blockdev.h>
+#include <drivers/disk/ahci.h>
+#include <drivers/disk/sata.h>
+#include <drivers/pcie.h>
+#include <errno.h>
+#include <mm/kmalloc.h>
+#include <mm/page.h>
+#include <util/debug.h>
+#include <util/string.h>
+
+#define ENABLE_NATIVE_COMMAND_QUEUING 1
+
+#define bdev_to_ata_disk(bd) (CONTAINER_OF((bd), ata_disk_t, bdev))
+#define SATA_SECTORS_PER_BLOCK (SATA_BLOCK_SIZE / ATA_SECTOR_SIZE)
+
+#define SATA_PCI_CLASS 0x1 /* 0x1 = mass storage device */
+#define SATA_PCI_SUBCLASS 0x6 /* 0x6 = sata */
+#define SATA_AHCI_INTERFACE 0x1 /* 0x1 = ahci */
+
+static hba_t *hba; /* host bus adapter */
+
+/* If NCQ, this is an outstanding tag bitmap.
+ * If standard, this is an outstanding command slot bitmap. */
+static uint32_t outstanding_requests[AHCI_MAX_NUM_PORTS] = {0};
+
+/* Each command slot on each port has a waitqueue for a thread waiting on a
+ * command to finish execution. */
+static ktqueue_t outstanding_request_queues[AHCI_MAX_NUM_PORTS]
+ [AHCI_COMMAND_HEADERS_PER_LIST];
+
+/* Each port has a waitqueue for a thread waiting on a new command slot to open
+ * up. */
+static ktqueue_t command_slot_queues[AHCI_MAX_NUM_PORTS];
+
+long sata_read_block(blockdev_t *bdev, char *buf, blocknum_t block,
+ size_t block_count);
+long sata_write_block(blockdev_t *bdev, const char *buf, blocknum_t block,
+ size_t block_count);
+
+/* sata_disk_ops - Block device operations for SATA devices. */
+static blockdev_ops_t sata_disk_ops = {
+ .read_block = sata_read_block,
+ .write_block = sata_write_block,
+};
+
+/* find_cmdslot - Checks various bitmaps to find the lowest index command slot
+ * that is free for a given port. */
+inline long find_cmdslot(hba_port_t *port)
+{
+ /* From 1.3.1: Free command slot will have corresponding bit clear in both
+ * px_sact and px_ci. To be safe, also check against our local copy of
+ * outstanding requests, in case a recently completed command is clear in
+ * the port's actual descriptor, but has not been processed by Weenix yet.
+ */
+ return __builtin_ctz(~(port->px_sact | port->px_ci |
+ outstanding_requests[PORT_INDEX(hba, port)]));
+}
+
+/* ensure_mapped - Wrapper for pt_map_range(). */
+void ensure_mapped(void *addr, size_t size)
+{
+ pt_map_range(pt_get(), (uintptr_t)PAGE_ALIGN_DOWN(addr) - PHYS_OFFSET,
+ (uintptr_t)PAGE_ALIGN_DOWN(addr),
+ (uintptr_t)PAGE_ALIGN_UP((uintptr_t)addr + size),
+ PT_WRITE | PT_PRESENT, PT_WRITE | PT_PRESENT);
+}
+
+kmutex_t because_qemu_doesnt_emulate_ahci_ncq_correctly;
+
+/* ahci_do_operation - Sends a command to the HBA to initiate a disk operation.
+ */
+long ahci_do_operation(hba_port_t *port, ssize_t lba, uint16_t count, void *buf,
+ int write)
+{
+ kmutex_lock(&because_qemu_doesnt_emulate_ahci_ncq_correctly);
+ KASSERT(count && buf);
+ // KASSERT(lba >= 0 && lba < (1L << 48));
+ KASSERT(lba >= 0 && lba < 1L << 23); //8388608
+
+ /* Obtain the port and the physical system memory in question. */
+ size_t port_index = PORT_INDEX(hba, port);
+
+ uint8_t ipl = intr_setipl(IPL_HIGH);
+
+ uint64_t physbuf = pt_virt_to_phys((uintptr_t)buf);
+
+ /* Get an available command slot. */
+ long command_slot;
+ while ((command_slot = find_cmdslot(port)) == -1)
+ {
+ sched_sleep_on(command_slot_queues + port_index);
+ }
+
+ /* Get corresponding command_header in the port's command_list. */
+ command_list_t *command_list =
+ (command_list_t *)(port->px_clb + PHYS_OFFSET);
+ command_header_t *command_header =
+ command_list->command_headers + command_slot;
+ memset(command_header, 0, sizeof(command_header_t));
+
+ /* Command setup: Header. */
+ command_header->cfl = sizeof(h2d_register_fis_t) / sizeof(uint32_t);
+ command_header->write = (uint8_t)write;
+ command_header->prdtl = (uint16_t)(
+ ALIGN_UP_POW_2(count, AHCI_SECTORS_PER_PRDT) / AHCI_SECTORS_PER_PRDT);
+ KASSERT(command_header->prdtl);
+
+ /* Command setup: Table. */
+ command_table_t *command_table =
+ (command_table_t *)(command_header->ctba + PHYS_OFFSET);
+ memset(command_table, 0, sizeof(command_table_t));
+
+ /* Command setup: Physical region descriptor table. */
+ prd_t *prdt = command_table->prdt;
+ /* Note that this loop is only called when the size of the data transfer is
+ * REALLY big. */
+ for (unsigned i = 0; i < command_header->prdtl - 1U; i++)
+ {
+ prdt->dbc = AHCI_MAX_PRDT_SIZE - 1;
+ prdt->dba = physbuf; /* Data from physical buffer. */
+ prdt->i = 1; /* Set interrupt on completion. */
+ physbuf +=
+ AHCI_MAX_PRDT_SIZE; /* Advance physical buffer for next prd. */
+ prdt++;
+ }
+ prdt->dbc = (uint32_t)(count % AHCI_SECTORS_PER_PRDT) * ATA_SECTOR_SIZE - 1;
+ prdt->dba = (uint64_t)physbuf;
+
+ /* Set up the particular h2d_register_fis command (the only one we use). */
+ h2d_register_fis_t *command_fis = &command_table->cfis.h2d_register_fis;
+ command_fis->fis_type = fis_type_h2d_register;
+ command_fis->c = 1;
+ command_fis->device = ATA_DEVICE_LBA_MODE;
+ command_fis->lba = (uint32_t)lba;
+ command_fis->lba_exp = (uint32_t)(lba >> 24);
+
+ /* NCQ: Allows the hardware to queue commands in its *own* order,
+ * independent of software delivery. */
+#if ENABLE_NATIVE_COMMAND_QUEUING
+ if (hba->ghc.cap.sncq)
+ {
+ /* For NCQ, sector count is stored in features. */
+ command_fis->features = (uint8_t)count;
+ command_fis->features_exp = (uint8_t)(count >> 8);
+
+ /* For NCQ, bits 7:3 of sector_count field specify NCQ tag. */
+ command_fis->sector_count = (uint16_t)(command_slot << 3);
+
+ /* Choose the appropriate NCQ read/write command. */
+ command_fis->command = (uint8_t)(write ? ATA_WRITE_FPDMA_QUEUED_COMMAND
+ : ATA_READ_FPDMA_QUEUED_COMMAND);
+ }
+ else
+ {
+ command_fis->sector_count = count;
+
+ command_fis->command = (uint8_t)(write ? ATA_WRITE_DMA_EXT_COMMAND
+ : ATA_READ_DMA_EXT_COMMAND);
+ }
+#else
+ /* For regular commands, simply set the command type and the sector count.
+ */
+ command_fis->sector_count = count;
+ command_fis->command =
+ (uint8_t)(write ? ATA_WRITE_DMA_EXT_COMMAND : ATA_READ_DMA_EXT_COMMAND);
+#endif
+
+ dbg(DBG_DISK, "initiating request on slot %ld to %s sectors [%lu, %lu)\n",
+ command_slot, write ? "write" : "read", lba, lba + count);
+
+ /* Locally mark that we sent out a command on the given command slot of the
+ * given port. */
+ outstanding_requests[port_index] |= (1 << command_slot);
+
+ /* Explicitly notify the port that a command is available for execution. */
+ port->px_sact |= (1 << command_slot);
+ port->px_ci |= (1 << command_slot);
+
+ /* Sleep until the command has been serviced. */
+ KASSERT(!curthr->kt_retval);
+
+ dbg(DBG_DISK,
+ "initiating request on slot %ld to %s sectors [%lu, %lu)...sleeping\n",
+ command_slot, write ? "write" : "read", lba, lba + count);
+ sched_sleep_on(outstanding_request_queues[port_index] + command_slot);
+ intr_setipl(ipl);
+ dbg(DBG_DISK, "completed request on slot %ld to %s sectors [%lu, %lu)\n",
+ command_slot, write ? "write" : "read", lba, lba + count);
+ kmutex_unlock(&because_qemu_doesnt_emulate_ahci_ncq_correctly);
+
+ long ret = (long)curthr->kt_retval;
+
+ return ret;
+}
+
+/* start_cmd - Start a port's DMA engines. See 10.3 of 1.3.1. */
+static inline void start_cmd(hba_port_t *port)
+{
+ while (port->px_cmd.cr)
+ ; /* Wait for command list DMA to stop running. */
+ port->px_cmd.fre = 1; /* Enable posting received FIS. */
+ port->px_cmd.st = 1; /* Enable processing the command list. */
+}
+
+/* stop_cmd - Stop a port's DMA engines. See 10.3 of 1.3.1. */
+static inline void stop_cmd(hba_port_t *port)
+{
+ port->px_cmd.st = 0; /* Stop processing the command list. */
+ while (port->px_cmd.cr)
+ ; /* Wait for command list DMA to stop running. */
+ port->px_cmd.fre = 0; /* Stop posting received FIS. */
+ while (port->px_cmd.fr)
+ ; /* Wait for FIS receive DMA to stop running. */
+}
+
+/* ahci_initialize_port */
+static void ahci_initialize_port(hba_port_t *port, unsigned int port_number,
+ uintptr_t ahci_base)
+{
+ dbg(DBG_DISK, "Initializing AHCI Port %d\n", port_number);
+
+ /* Pretty sure this is unnecessary. */
+ // port->px_serr = port->px_serr;
+
+ /* Make sure the port is not doing any DMA. */
+ stop_cmd(port);
+
+ /* Pretty sure this is unnecessary. */
+ // port->px_serr = (unsigned) -1;
+
+ /* Determine and set the command list and received FIS base addresses in the
+ * port's descriptor. */
+ command_list_t *command_list =
+ (command_list_t *)AHCI_COMMAND_LIST_ARRAY_BASE(ahci_base) + port_number;
+ received_fis_t *received_fis =
+ (received_fis_t *)AHCI_RECEIVED_FIS_ARRAY_BASE(ahci_base) + port_number;
+
+ port->px_clb = (uint64_t)command_list - PHYS_OFFSET;
+ port->px_fb = (uint64_t)received_fis - PHYS_OFFSET;
+ port->px_ie =
+ px_interrupt_enable_all_enabled; /* FLAG: Weenix does not need to enable
+ * all interrupts. Aside from dhrs and
+ * sdbs, I think we could either
+ * disable others,
+ * or tell the handler to panic if
+ * other interrupts are encountered. */
+ port->px_is =
+ px_interrupt_status_clear; /* RWC: Read / Write '1' to Clear. */
+
+ /* Determine and set the command tables.
+ * For each header, set its corresponding table and set up its queue. */
+ command_table_t *port_command_table_array_base =
+ (command_table_t *)AHCI_COMMAND_TABLE_ARRAY_BASE(ahci_base) +
+ port_number * AHCI_COMMAND_HEADERS_PER_LIST;
+ for (unsigned i = 0; i < AHCI_COMMAND_HEADERS_PER_LIST; i++)
+ {
+ command_list->command_headers[i].ctba =
+ (uint64_t)(port_command_table_array_base + i) - PHYS_OFFSET;
+ sched_queue_init(outstanding_request_queues[port_number] + i);
+ }
+
+ /* Start the queue to wait for an open command slot. */
+ sched_queue_init(command_slot_queues + port_number);
+
+ /* For SATA disks, allocate, setup, and register the disk / block device. */
+ if (port->px_sig == SATA_SIG_ATA)
+ {
+ dbg(DBG_DISK, "\tAdding SATA Disk Drive at Port %d\n", port_number);
+ ata_disk_t *disk = kmalloc(sizeof(ata_disk_t));
+ disk->port = port;
+ disk->bdev.bd_id = MKDEVID(DISK_MAJOR, port_number);
+ disk->bdev.bd_ops = &sata_disk_ops;
+ list_link_init(&disk->bdev.bd_link);
+ long ret = blockdev_register(&disk->bdev);
+ KASSERT(!ret);
+ }
+ else
+ {
+ /* FLAG: Should we just check sig first and save some work on unknown
+ * devices? */
+ dbg(DBG_DISK, "\tunknown device signature: 0x%x\n", port->px_sig);
+ }
+
+ /* Start the port's DMA engines and allow it to start servicing commands. */
+ start_cmd(port);
+
+ /* RWC: Write back to clear errors one more time. FLAG: WHY?! */
+ // port->px_serr = port->px_serr;
+}
+
+/* ahci_initialize_hba - Called at initialization to set up hba-related fields.
+ */
+void ahci_initialize_hba()
+{
+ kmutex_init(&because_qemu_doesnt_emulate_ahci_ncq_correctly);
+
+ /* Get the HBA controller for the SATA device. */
+ pcie_device_t *dev =
+ pcie_lookup(SATA_PCI_CLASS, SATA_PCI_SUBCLASS, SATA_AHCI_INTERFACE);
+
+ /* Set bit 2 to enable memory and I/O requests.
+ * This actually doesn't seem to be necessary...
+ * See: 2.1.2, AHCI SATA 1.3.1. */
+ // dev->standard.command |= 0x4;
+
+ /* Traverse the pcie_device_t's capabilities to look for an MSI capability.
+ */
+ KASSERT(dev->standard.capabilities_ptr & PCI_CAPABILITY_PTR_MASK);
+ pci_capability_t *cap =
+ (pci_capability_t *)((uintptr_t)dev + (dev->standard.capabilities_ptr &
+ PCI_CAPABILITY_PTR_MASK));
+ while (cap->id != PCI_MSI_CAPABILITY_ID)
+ {
+ KASSERT(cap->next_cap && "couldn't find msi control for ahci device");
+ cap = (pci_capability_t *)((uintptr_t)dev +
+ (cap->next_cap & PCI_CAPABILITY_PTR_MASK));
+ }
+ msi_capability_t *msi_cap = (msi_capability_t *)cap;
+
+ /* Set MSI Enable to turn on MSI. */
+ msi_cap->control.msie = 1;
+
+ /* For more info on MSI, consult Intel 3A 10.11.1, and also 2.3 of the 1.3.1
+ * spec. */
+
+ /* Set up MSI for processor 1, with interrupt vector INTR_DISK_PRIMARY.
+ * TODO: Check MSI setup details to determine if MSI can be handled more
+ * efficiently in SMP.
+ */
+ if (msi_cap->control.c64)
+ {
+ msi_cap->address_data.ad64.addr = MSI_ADDRESS_FOR(1);
+ msi_cap->address_data.ad64.data = MSI_DATA_FOR(INTR_DISK_PRIMARY);
+ }
+ else
+ {
+ msi_cap->address_data.ad32.addr = MSI_ADDRESS_FOR(1);
+ msi_cap->address_data.ad32.data = MSI_DATA_FOR(INTR_DISK_PRIMARY);
+ }
+
+ KASSERT(dev && "Could not find AHCI Controller");
+ dbg(DBG_DISK, "Found AHCI Controller\n");
+
+ /* bar = base address register. The last bar points to base memory for the
+ * host bus adapter. */
+ hba = (hba_t *)(PHYS_OFFSET + dev->standard.bar[5]);
+
+ /* Create a page table mapping for the hba. */
+ ensure_mapped(hba, sizeof(hba_t));
+
+ /* This seems to do nothing, because interrupt_line is never set, and MSIE
+ * is set. */
+ // intr_map(dev->standard.interrupt_line, INTR_DISK_PRIMARY);
+
+ /* Allocate space for what will become the command lists and received FISs
+ * for each port. */
+ uintptr_t ahci_base = (uintptr_t)page_alloc_n(AHCI_SIZE_PAGES);
+ memset((void *)ahci_base, 0, AHCI_SIZE_PAGES * PAGE_SIZE);
+
+ KASSERT(ahci_base);
+ /* Set AHCI Enable bit.
+ * Actually this bit appears to be read-only (see 3.1.2 AE and 3.1.1 SAM).
+ * I do get a "mis-aligned write" complaint when I try to manually set it.
+ */
+ KASSERT(hba->ghc.ghc.ae);
+
+ /* Temporarily clear Interrupt Enable bit before setting up ports. */
+ hba->ghc.ghc.ie = 0;
+
+ dbg(DBG_DISK, "ahci ncq supported: %s\n",
+ hba->ghc.cap.sncq ? "true" : "false");
+
+ /* Initialize each of the available ports. */
+ uint32_t ports_implemented = hba->ghc.pi;
+ KASSERT(ports_implemented);
+ while (ports_implemented)
+ {
+ unsigned port_number = __builtin_ctz(ports_implemented);
+ ports_implemented &= ~(1 << port_number);
+ ahci_initialize_port(hba->ports + port_number, port_number, ahci_base);
+ }
+
+ /* Clear any outstanding interrupts from any ports. */
+ hba->ghc.is = (uint32_t)-1;
+
+ /* Restore Interrupt Enable bit. */
+ hba->ghc.ghc.ie = 1;
+}
+
+/* ahci_interrupt_handler - Service an interrupt that was raised by the HBA.
+ */
+static long ahci_interrupt_handler(regs_t *regs)
+{
+ /* Check interrupt status bitmap for ports to service. */
+ while (hba->ghc.is)
+ {
+ /* Get a port from the global interrupt status bitmap. */
+ unsigned port_index = __builtin_ctz(hba->ghc.is);
+
+ /* Get the port descriptor from the HBA's ports array. */
+ hba_port_t *port = hba->ports + port_index;
+
+ /* Beware: If a register is marked "RWC" in the spec, you must clear it
+ * by writing 1. This is rather understated in the specification. */
+
+ /* Clear the cause of the interrupt.
+ * See 5.6.2 and 5.6.4 in the 1.3.1 spec for confirmation of the FIS and
+ * corresponding interrupt that are used depending on the type of
+ * command.
+ */
+
+#if ENABLE_NATIVE_COMMAND_QUEUING
+ if (hba->ghc.cap.sncq)
+ {
+ KASSERT(port->px_is.bits.sdbs);
+ port->px_is.bits.sdbs = 1;
+ }
+ else
+ {
+ KASSERT(port->px_is.bits.dhrs);
+ port->px_is.bits.dhrs = 1;
+ }
+#else
+ KASSERT(port->px_is.bits.dhrs);
+ port->px_is.bits.dhrs = 1;
+#endif
+
+ /* Clear the port's bit on the global interrupt status bitmap, to
+ * indicate we have handled it. */
+ /* Note: Changed from ~ to regular, because this register is RWC. */
+ hba->ghc.is &= (1 << port_index);
+
+ /* Get the list of commands still outstanding. */
+#if ENABLE_NATIVE_COMMAND_QUEUING
+ /* If NCQ, use SACT register. */
+ uint32_t active = hba->ghc.cap.sncq ? port->px_sact : port->px_ci;
+#else
+ /* If not NCQ, use CI register. */
+ uint32_t active = port->px_ci;
+#endif
+
+ /* Compare the active commands against those we actually sent out to get
+ * completed commands. */
+ uint32_t completed = outstanding_requests[port_index] &
+ ~(outstanding_requests[port_index] & active);
+ /* Handle each completed command: */
+ while (completed)
+ {
+ uint32_t slot = __builtin_ctz(completed);
+
+ /* Wake up the thread that was waiting on that command. */
+ kthread_t *thr;
+ sched_wakeup_on(&outstanding_request_queues[port_index][slot],
+ &thr);
+
+ /* Mark the command as available. */
+ completed &= ~(1 << slot);
+ outstanding_requests[port_index] &= ~(1 << slot);
+
+ /* TODO: Wake up threads that were waiting for a command slot to
+ * free up on the port. */
+ }
+ }
+ return 0;
+}
+
+void sata_init()
+{
+ intr_register(INTR_DISK_PRIMARY, ahci_interrupt_handler);
+ ahci_initialize_hba();
+}
+
+/**
+ * Read the given number of blocks from a block device starting at
+ * a given block number into a buffer.
+ *
+ * To do this, you will need to call ahci_do_operation(). SATA devices
+ * conduct operations in terms of sectors, rather than blocks, thus
+ * you will need to convert the arguments passed in to be in terms of
+ * sectors.
+ *
+ * @param bdev block device to read from
+ * @param buf buffer to write to
+ * @param block block number to start reading at
+ * @param block_count the number of blocks to read
+ * @return 0 on success and <0 on error
+ */
+long sata_read_block(blockdev_t *bdev, char *buf, blocknum_t block,
+ size_t block_count)
+{
+ NOT_YET_IMPLEMENTED("DRIVERS: ***none***");
+ return -1;
+}
+
+/**
+ * Writes a a given number of blocks from a buffer to a block device
+ * starting at a given block. This function should be very similar to what
+ * is done in sata_read, save for the write argument that is passed to
+ * ahci_do_operation().
+ *
+ * @param bdev block device to write to
+ * @param buf buffer to read from
+ * @param block block number to start writing at
+ * @param block_count the number of blocks to write
+ * @return 0 on success and <0 on error
+ */
+long sata_write_block(blockdev_t *bdev, const char *buf, blocknum_t block,
+ size_t block_count)
+{
+ NOT_YET_IMPLEMENTED("DRIVERS: ***none***");
+ return -1;
+}
diff --git a/kernel/drivers/keyboard.c b/kernel/drivers/keyboard.c
new file mode 100644
index 0000000..c0c4b5e
--- /dev/null
+++ b/kernel/drivers/keyboard.c
@@ -0,0 +1,208 @@
+#include "drivers/keyboard.h"
+
+#include "drivers/tty/tty.h"
+
+#include "main/interrupt.h"
+#include "main/io.h"
+
+#define IRQ_KEYBOARD 1
+
+/* Indicates that one of these is "being held down" */
+#define SHIFT_MASK 0x1
+#define CTRL_MASK 0x2
+/* Indicates that an escape code was the previous key received */
+#define ESC_MASK 0x4
+static int curmask = 0;
+
+/* Where to read from to get scancodes */
+#define KEYBOARD_IN_PORT 0x60
+#define KEYBOARD_CMD_PORT 0x61
+
+/* Scancodes for special keys */
+#define LSHIFT 0x2a
+#define RSHIFT 0x36
+#define CTRL 0x1d
+/* Right ctrl is escaped */
+/* Our keyboard driver totally ignores ALT */
+
+#define ESC0 0xe0
+#define ESC1 0xe1
+
+/* If the scancode & BREAK_MASK, it's a break code; otherwise, it's a make code
+ */
+#define BREAK_MASK 0x80
+
+#define NORMAL_KEY_HIGH 0x39
+
+/* Some sneaky value to indicate we don't actually pass anything to the terminal
+ */
+#define NO_CHAR 0xff
+
+#define F1_SCANCODE 0x3b
+#define F12_SCANCODE (F1_SCANCODE + 11)
+
+/* Scancode tables copied from
+ http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html */
+
+/* The scancode table for "normal" scancodes - from 02 to 39 */
+/* Unsupported chars are symbolized by \0 */
+static const char *normal_scancodes =
+ "\0" /* Error */
+ "\e" /* Escape key */
+ "1234567890-=" /* Top row */
+ "\b" /* Backspace */
+ "\tqwertyuiop[]\n" /* Next row - ish */
+ "\0" /* Left ctrl */
+ "asdfghjkl;\'`"
+ "\0" /* Lshift */
+ "\\"
+ "zxcvbnm,./"
+ "\0\0\0" /* Rshift, prtscrn, Lalt */
+ " "; /* Space bar */
+/* As above, but if shift is pressed */
+static const char *shift_scancodes =
+ "\0"
+ "\e"
+ "!@#$%^&*()_+"
+ "\b"
+ "\tQWERTYUIOP{}\n"
+ "\0"
+ "ASDFGHJKL:\"~"
+ "\0"
+ "|"
+ "ZXCVBNM<>?"
+ "\0\0\0"
+ " ";
+
+static keyboard_char_handler_t keyboard_handler = NULL;
+
+/* This is the function we register with the interrupt handler - it reads the
+ * scancode and, if appropriate, call's the tty's receive_char function */
+static long keyboard_intr_handler(regs_t *regs)
+{
+ uint8_t sc; /* The scancode we receive */
+ int break_code; /* Was it a break code */
+ /* the resulting character ('\0' -> ignored char) */
+ uint8_t c = NO_CHAR;
+ /* Get the scancode */
+ sc = inb(KEYBOARD_IN_PORT);
+ /* Separate out the break code */
+ break_code = sc & BREAK_MASK;
+ sc &= ~BREAK_MASK;
+
+ /* dbg(DBG_KB, ("scancode 0x%x, break 0x%x\n", sc, break_code)); */
+
+ /* The order of this conditional is very, very tricky - be careful when
+ * editing! */
+
+ /* Most break codes are ignored */
+ if (break_code)
+ {
+ /* Shift/ctrl release */
+ if (sc == LSHIFT || sc == RSHIFT)
+ {
+ curmask &= ~SHIFT_MASK;
+ }
+ else if (sc == CTRL)
+ {
+ curmask &= ~CTRL_MASK;
+ }
+ }
+ /* Check for the special keys */
+ else if (sc == LSHIFT || sc == RSHIFT)
+ {
+ curmask |= SHIFT_MASK;
+ }
+ else if (sc == CTRL)
+ {
+ curmask |= CTRL_MASK;
+ }
+ /* All escaped keys past this point (anything except right shift and right
+ * ctrl) will be ignored */
+ else if (curmask & ESC_MASK)
+ {
+ /* Escape mask only lasts for one key */
+ curmask &= ~ESC_MASK;
+ }
+ /* Now check for escape code */
+ else if (sc == ESC0 || sc == ESC1)
+ {
+ curmask |= ESC_MASK;
+ }
+
+ else if (sc >= F1_SCANCODE && sc <= F12_SCANCODE)
+ {
+ c = (uint8_t)(F1 + (sc - F1_SCANCODE));
+ }
+ /* Check for Ctrl+Backspace which indicates scroll down */
+ else if ((curmask & CTRL_MASK) && (curmask & SHIFT_MASK) &&
+ sc == SCROLL_DOWN)
+ {
+ c = SCROLL_DOWN_PAGE;
+ }
+
+ else if ((curmask & CTRL_MASK) && (curmask & SHIFT_MASK) &&
+ sc == SCROLL_UP)
+ {
+ c = SCROLL_UP_PAGE;
+ }
+
+ else if ((curmask & CTRL_MASK) && sc == SCROLL_DOWN)
+ {
+ c = SCROLL_DOWN;
+ }
+ /* Check for Ctrl+Enter which indicates scroll down */
+ else if ((curmask & CTRL_MASK) && sc == SCROLL_UP)
+ {
+ c = SCROLL_UP;
+ }
+ /* Check to make sure the key isn't high enough that it won't be found in
+ * tables */
+ else if (sc > NORMAL_KEY_HIGH)
+ {
+ /* ignore */
+ }
+ /* Control characters */
+ else if (curmask & CTRL_MASK)
+ {
+ /* Because of the way ASCII works, the control chars are based on the
+ * values of the shifted chars produced without control */
+ c = (uint8_t)shift_scancodes[sc];
+ /* Range of chars that have corresponding control chars */
+ if (c >= 0x40 && c < 0x60)
+ {
+ c -= 0x40;
+ }
+ else
+ {
+ c = NO_CHAR;
+ }
+ }
+ /* Capitals */
+ else if (curmask & SHIFT_MASK)
+ {
+ c = (uint8_t)shift_scancodes[sc];
+ }
+ else
+ {
+ c = (uint8_t)normal_scancodes[sc];
+ }
+
+ if (c != NO_CHAR)
+ {
+ keyboard_handler(c);
+ }
+ else
+ {
+ // panic("get rid of me: char was: %c (%d) (%x)\n", c, c, c);
+ }
+ dbg(DBG_KB, "received scancode 0x%x; resolved to char 0x%x\n", sc, c);
+ return 0;
+}
+
+void keyboard_init(keyboard_char_handler_t handler)
+{
+ intr_map(IRQ_KEYBOARD, INTR_KEYBOARD);
+ intr_register(INTR_KEYBOARD, keyboard_intr_handler);
+ keyboard_handler = handler;
+}
diff --git a/kernel/drivers/memdevs.c b/kernel/drivers/memdevs.c
new file mode 100644
index 0000000..4898614
--- /dev/null
+++ b/kernel/drivers/memdevs.c
@@ -0,0 +1,108 @@
+#include "errno.h"
+#include "globals.h"
+
+#include "util/debug.h"
+#include "util/string.h"
+
+#include "mm/kmalloc.h"
+#include "mm/mobj.h"
+
+#include "drivers/chardev.h"
+
+#include "vm/anon.h"
+
+#include "fs/vnode.h"
+
+static ssize_t null_read(chardev_t *dev, size_t pos, void *buf, size_t count);
+
+static ssize_t null_write(chardev_t *dev, size_t pos, const void *buf,
+ size_t count);
+
+static ssize_t zero_read(chardev_t *dev, size_t pos, void *buf, size_t count);
+
+static long zero_mmap(vnode_t *file, mobj_t **ret);
+
+chardev_ops_t null_dev_ops = {.read = null_read,
+ .write = null_write,
+ .mmap = NULL,
+ .fill_pframe = NULL,
+ .flush_pframe = NULL};
+
+chardev_ops_t zero_dev_ops = {.read = zero_read,
+ .write = null_write,
+ .mmap = zero_mmap,
+ .fill_pframe = NULL,
+ .flush_pframe = NULL};
+
+/**
+ * The char device code needs to know about these mem devices, so create
+ * chardev_t's for null and zero, fill them in, and register them.
+ *
+ * Use kmalloc, MEM_NULL_DEVID, MEM_ZERO_DEVID, and chardev_register.
+ * See dev.h for device ids to use with MKDEVID.
+ */
+void memdevs_init()
+{
+ NOT_YET_IMPLEMENTED("DRIVERS: ***none***");
+}
+
+/**
+ * Reads a given number of bytes from the null device into a
+ * buffer. Any read performed on the null device should read 0 bytes.
+ *
+ * @param dev the null device
+ * @param pos the offset to read from; should be ignored
+ * @param buf the buffer to read into
+ * @param count the maximum number of bytes to read
+ * @return the number of bytes read, which should be 0
+ */
+static ssize_t null_read(chardev_t *dev, size_t pos, void *buf, size_t count)
+{
+ NOT_YET_IMPLEMENTED("DRIVERS: ***none***");
+ return -ENOMEM;
+}
+
+/**
+ * Writes a given number of bytes to the null device from a
+ * buffer. Writing to the null device should _ALWAYS_ be successful
+ * and write the maximum number of bytes.
+ *
+ * @param dev the null device
+ * @param pos offset the offset to write to; should be ignored
+ * @param buf buffer to read from
+ * @param count the maximum number of bytes to write
+ * @return the number of bytes written, which should be `count`
+ */
+static ssize_t null_write(chardev_t *dev, size_t pos, const void *buf,
+ size_t count)
+{
+ NOT_YET_IMPLEMENTED("DRIVERS: ***none***");
+ return -ENOMEM;
+}
+
+/**
+ * Reads a given number of bytes from the zero device into a
+ * buffer. Any read from the zero device should be a series of zeros.
+ *
+ * @param dev the zero device
+ * @param pos the offset to start reading from; should be ignored
+ * @param buf the buffer to write to
+ * @param count the maximum number of bytes to read
+ * @return the number of bytes read. Hint: should always read the maximum
+ * number of bytes
+ */
+static ssize_t zero_read(chardev_t *dev, size_t pos, void *buf, size_t count)
+{
+ NOT_YET_IMPLEMENTED("DRIVERS: ***none***");
+ return 0;
+}
+
+/**
+ * Unlike in s5fs_mmap(), you can't necessarily use the file's underlying mobj.
+ * Instead, you should simply provide an anonymous object to ret.
+ */
+static long zero_mmap(vnode_t *file, mobj_t **ret)
+{
+ NOT_YET_IMPLEMENTED("VM: ***none***");
+ return -1;
+}
diff --git a/kernel/drivers/pcie.c b/kernel/drivers/pcie.c
new file mode 100644
index 0000000..6003eab
--- /dev/null
+++ b/kernel/drivers/pcie.c
@@ -0,0 +1,77 @@
+#include "drivers/pcie.h"
+#include <drivers/pcie.h>
+#include <main/acpi.h>
+#include <mm/kmalloc.h>
+#include <mm/pagetable.h>
+#include <util/debug.h>
+
+#define MCFG_SIGNATURE (*(uint32_t *)"MCFG")
+static uintptr_t pcie_base_addr;
+
+typedef struct pcie_table
+{
+ pcie_device_t devices[PCI_NUM_BUSES][PCI_NUM_DEVICES_PER_BUS]
+ [PCI_NUM_FUNCTIONS_PER_DEVICE];
+} pcie_table_t;
+
+static pcie_table_t *pcie_table;
+
+#define PCIE_DEV(bus, device, func) \
+ (&pcie_table->devices[(bus)][(device)][(func)])
+static list_t pcie_wrapper_list;
+
+void pci_init(void)
+{
+ // TODO document; needs -machine type=q35 flag in qemu!
+ void *table = acpi_table(MCFG_SIGNATURE, 0);
+ KASSERT(table);
+ pcie_base_addr = *(uintptr_t *)((uintptr_t)table + 44) + PHYS_OFFSET;
+ pcie_table = (pcie_table_t *)pcie_base_addr;
+ pt_map_range(pt_get(), pcie_base_addr - PHYS_OFFSET, pcie_base_addr,
+ pcie_base_addr + PAGE_SIZE_1GB, PT_WRITE | PT_PRESENT,
+ PT_WRITE | PT_PRESENT);
+
+ list_init(&pcie_wrapper_list);
+ for (unsigned bus = 0; bus < PCI_NUM_BUSES; bus++)
+ {
+ for (unsigned device = 0; device < PCI_NUM_DEVICES_PER_BUS; device++)
+ {
+ unsigned int max_functions =
+ (PCIE_DEV(bus, device, 0)->standard.header_type & 0x80)
+ ? PCI_NUM_DEVICES_PER_BUS
+ : 1;
+ for (unsigned function = 0; function < max_functions; function++)
+ {
+ pcie_device_t *dev = PCIE_DEV(bus, device, function);
+ if (!dev->standard.vendor_id ||
+ dev->standard.vendor_id == (uint16_t)-1)
+ continue;
+ pcie_device_wrapper_t *wrapper =
+ kmalloc(sizeof(pcie_device_wrapper_t));
+ wrapper->dev = dev;
+ wrapper->class = dev->standard.class;
+ wrapper->subclass = dev->standard.subclass;
+ wrapper->interface = dev->standard.prog_if;
+ list_link_init(&wrapper->link);
+ list_insert_tail(&pcie_wrapper_list, &wrapper->link);
+ }
+ }
+ }
+}
+
+pcie_device_t *pcie_lookup(uint8_t class, uint8_t subclass, uint8_t interface)
+{
+ list_iterate(&pcie_wrapper_list, wrapper, pcie_device_wrapper_t, link)
+ {
+ /* verify the class subclass and interface are correct */
+ if (((class == PCI_LOOKUP_WILDCARD) || (wrapper->class == class)) &&
+ ((subclass == PCI_LOOKUP_WILDCARD) ||
+ (wrapper->subclass == subclass)) &&
+ ((interface == PCI_LOOKUP_WILDCARD) ||
+ (wrapper->interface == interface)))
+ {
+ return wrapper->dev;
+ }
+ }
+ return NULL;
+}
diff --git a/kernel/drivers/screen.c b/kernel/drivers/screen.c
new file mode 100644
index 0000000..a14ad08
--- /dev/null
+++ b/kernel/drivers/screen.c
@@ -0,0 +1,513 @@
+#include <boot/config.h>
+#include <boot/multiboot_macros.h>
+#include <drivers/screen.h>
+#include <multiboot.h>
+#include <types.h>
+#include <util/debug.h>
+#include <util/string.h>
+
+#ifdef __VGABUF___
+
+#define BITMAP_HEIGHT 13
+
+// https://stackoverflow.com/questions/2156572/c-header-file-with-bitmapped-fonts
+unsigned const char bitmap_letters[95][BITMAP_HEIGHT] = {
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00}, // space :32
+ {0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x18}, // ! :33
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x36, 0x36,
+ 0x36},
+ {0x00, 0x00, 0x00, 0x66, 0x66, 0xff, 0x66, 0x66, 0xff, 0x66, 0x66, 0x00,
+ 0x00},
+ {0x00, 0x00, 0x18, 0x7e, 0xff, 0x1b, 0x1f, 0x7e, 0xf8, 0xd8, 0xff, 0x7e,
+ 0x18},
+ {0x00, 0x00, 0x0e, 0x1b, 0xdb, 0x6e, 0x30, 0x18, 0x0c, 0x76, 0xdb, 0xd8,
+ 0x70},
+ {0x00, 0x00, 0x7f, 0xc6, 0xcf, 0xd8, 0x70, 0x70, 0xd8, 0xcc, 0xcc, 0x6c,
+ 0x38},
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x1c, 0x0c,
+ 0x0e},
+ {0x00, 0x00, 0x0c, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x18,
+ 0x0c},
+ {0x00, 0x00, 0x30, 0x18, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x18,
+ 0x30},
+ {0x00, 0x00, 0x00, 0x00, 0x99, 0x5a, 0x3c, 0xff, 0x3c, 0x5a, 0x99, 0x00,
+ 0x00},
+ {0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0xff, 0xff, 0x18, 0x18, 0x18, 0x00,
+ 0x00},
+ {0x00, 0x00, 0x30, 0x18, 0x1c, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x00, 0x00, 0x38, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x60, 0x60, 0x30, 0x30, 0x18, 0x18, 0x0c, 0x0c, 0x06, 0x06, 0x03,
+ 0x03},
+ {0x00, 0x00, 0x3c, 0x66, 0xc3, 0xe3, 0xf3, 0xdb, 0xcf, 0xc7, 0xc3, 0x66,
+ 0x3c},
+ {0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78, 0x38,
+ 0x18},
+ {0x00, 0x00, 0xff, 0xc0, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x03, 0xe7,
+ 0x7e},
+ {0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x07, 0x7e, 0x07, 0x03, 0x03, 0xe7,
+ 0x7e},
+ {0x00, 0x00, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xff, 0xcc, 0x6c, 0x3c, 0x1c,
+ 0x0c},
+ {0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x07, 0xfe, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xff},
+ {0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xc7, 0xfe, 0xc0, 0xc0, 0xc0, 0xe7,
+ 0x7e},
+ {0x00, 0x00, 0x30, 0x30, 0x30, 0x30, 0x18, 0x0c, 0x06, 0x03, 0x03, 0x03,
+ 0xff},
+ {0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xe7, 0x7e, 0xe7, 0xc3, 0xc3, 0xe7,
+ 0x7e},
+ {0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x03, 0x7f, 0xe7, 0xc3, 0xc3, 0xe7,
+ 0x7e},
+ {0x00, 0x00, 0x00, 0x38, 0x38, 0x00, 0x00, 0x38, 0x38, 0x00, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x00, 0x30, 0x18, 0x1c, 0x1c, 0x00, 0x00, 0x1c, 0x1c, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x00, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x60, 0x30, 0x18, 0x0c,
+ 0x06},
+ {0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x00, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x03, 0x06, 0x0c, 0x18, 0x30,
+ 0x60},
+ {0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x18, 0x0c, 0x06, 0x03, 0xc3, 0xc3,
+ 0x7e},
+ {0x00, 0x00, 0x3f, 0x60, 0xcf, 0xdb, 0xd3, 0xdd, 0xc3, 0x7e, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xff, 0xc3, 0xc3, 0xc3, 0x66, 0x3c,
+ 0x18},
+ {0x00, 0x00, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7,
+ 0xfe},
+ {0x00, 0x00, 0x7e, 0xe7, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe7,
+ 0x7e},
+ {0x00, 0x00, 0xfc, 0xce, 0xc7, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc7, 0xce,
+ 0xfc},
+ {0x00, 0x00, 0xff, 0xc0, 0xc0, 0xc0, 0xc0, 0xfc, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xff},
+ {0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xfc, 0xc0, 0xc0, 0xc0,
+ 0xff},
+ {0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xcf, 0xc0, 0xc0, 0xc0, 0xc0, 0xe7,
+ 0x7e},
+ {0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xff, 0xc3, 0xc3, 0xc3, 0xc3,
+ 0xc3},
+ {0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x7e},
+ {0x00, 0x00, 0x7c, 0xee, 0xc6, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06},
+ {0x00, 0x00, 0xc3, 0xc6, 0xcc, 0xd8, 0xf0, 0xe0, 0xf0, 0xd8, 0xcc, 0xc6,
+ 0xc3},
+ {0x00, 0x00, 0xff, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0},
+ {0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xdb, 0xff, 0xff, 0xe7,
+ 0xc3},
+ {0x00, 0x00, 0xc7, 0xc7, 0xcf, 0xcf, 0xdf, 0xdb, 0xfb, 0xf3, 0xf3, 0xe3,
+ 0xe3},
+ {0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xe7,
+ 0x7e},
+ {0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7,
+ 0xfe},
+ {0x00, 0x00, 0x3f, 0x6e, 0xdf, 0xdb, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0x66,
+ 0x3c},
+ {0x00, 0x00, 0xc3, 0xc6, 0xcc, 0xd8, 0xf0, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7,
+ 0xfe},
+ {0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x07, 0x7e, 0xe0, 0xc0, 0xc0, 0xe7,
+ 0x7e},
+ {0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0xff},
+ {0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3,
+ 0xc3},
+ {0x00, 0x00, 0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3,
+ 0xc3},
+ {0x00, 0x00, 0xc3, 0xe7, 0xff, 0xff, 0xdb, 0xdb, 0xc3, 0xc3, 0xc3, 0xc3,
+ 0xc3},
+ {0x00, 0x00, 0xc3, 0x66, 0x66, 0x3c, 0x3c, 0x18, 0x3c, 0x3c, 0x66, 0x66,
+ 0xc3},
+ {0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x3c, 0x66, 0x66,
+ 0xc3},
+ {0x00, 0x00, 0xff, 0xc0, 0xc0, 0x60, 0x30, 0x7e, 0x0c, 0x06, 0x03, 0x03,
+ 0xff},
+ {0x00, 0x00, 0x3c, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x3c},
+ {0x00, 0x03, 0x03, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x18, 0x30, 0x30, 0x60,
+ 0x60},
+ {0x00, 0x00, 0x3c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x3c},
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0x66, 0x3c,
+ 0x18},
+ {0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x38, 0x30,
+ 0x70},
+ {0x00, 0x00, 0x7f, 0xc3, 0xc3, 0x7f, 0x03, 0xc3, 0x7e, 0x00, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x00, 0xfe, 0xc3, 0xc3, 0xc3, 0xc3, 0xfe, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0},
+ {0x00, 0x00, 0x7e, 0xc3, 0xc0, 0xc0, 0xc0, 0xc3, 0x7e, 0x00, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x00, 0x7f, 0xc3, 0xc3, 0xc3, 0xc3, 0x7f, 0x03, 0x03, 0x03, 0x03,
+ 0x03},
+ {0x00, 0x00, 0x7f, 0xc0, 0xc0, 0xfe, 0xc3, 0xc3, 0x7e, 0x00, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xfc, 0x30, 0x30, 0x30, 0x33,
+ 0x1e},
+ {0x7e, 0xc3, 0x03, 0x03, 0x7f, 0xc3, 0xc3, 0xc3, 0x7e, 0x00, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xfe, 0xc0, 0xc0, 0xc0,
+ 0xc0},
+ {0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18,
+ 0x00},
+ {0x38, 0x6c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x00, 0x00, 0x0c,
+ 0x00},
+ {0x00, 0x00, 0xc6, 0xcc, 0xf8, 0xf0, 0xd8, 0xcc, 0xc6, 0xc0, 0xc0, 0xc0,
+ 0xc0},
+ {0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x78},
+ {0x00, 0x00, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xfe, 0x00, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xfc, 0x00, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00,
+ 0x00},
+ {0xc0, 0xc0, 0xc0, 0xfe, 0xc3, 0xc3, 0xc3, 0xc3, 0xfe, 0x00, 0x00, 0x00,
+ 0x00},
+ {0x03, 0x03, 0x03, 0x7f, 0xc3, 0xc3, 0xc3, 0xc3, 0x7f, 0x00, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, 0xfe, 0x00, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x00, 0xfe, 0x03, 0x03, 0x7e, 0xc0, 0xc0, 0x7f, 0x00, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x00, 0x1c, 0x36, 0x30, 0x30, 0x30, 0x30, 0xfc, 0x30, 0x30, 0x30,
+ 0x00},
+ {0x00, 0x00, 0x7e, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x00, 0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3, 0xc3, 0x00, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x00, 0xc3, 0xe7, 0xff, 0xdb, 0xc3, 0xc3, 0xc3, 0x00, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x00, 0xc3, 0x66, 0x3c, 0x18, 0x3c, 0x66, 0xc3, 0x00, 0x00, 0x00,
+ 0x00},
+ {0xc0, 0x60, 0x60, 0x30, 0x18, 0x3c, 0x66, 0x66, 0xc3, 0x00, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x00, 0xff, 0x60, 0x30, 0x18, 0x0c, 0x06, 0xff, 0x00, 0x00, 0x00,
+ 0x00},
+ {0x00, 0x00, 0x0f, 0x18, 0x18, 0x18, 0x38, 0xf0, 0x38, 0x18, 0x18, 0x18,
+ 0x0f},
+ {0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x18},
+ {0x00, 0x00, 0xf0, 0x18, 0x18, 0x18, 0x1c, 0x0f, 0x1c, 0x18, 0x18, 0x18,
+ 0xf0},
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x8f, 0xf1, 0x60, 0x00, 0x00,
+ 0x00},
+};
+
+#define DOUBLE_BUFFERING 0
+
+#define BITWISE_TERNARY(condition, x, y) \
+ (!!(condition) * (x) + !(condition) * (y))
+
+static uint32_t *fb;
+static uint32_t fb_width;
+static uint32_t fb_height;
+static uint32_t fb_pitch;
+
+static uint32_t *fb_buffer;
+
+void screen_init()
+{
+ static long inited = 0;
+ if (inited)
+ return;
+ inited = 1;
+
+ struct multiboot_tag_framebuffer *fb_tag = NULL;
+ for (struct multiboot_tag *tag =
+ (struct multiboot_tag *)((uintptr_t)(mb_tag + 1) + PHYS_OFFSET);
+ tag->type != MULTIBOOT_TAG_TYPE_END; tag += TAG_SIZE(tag->size))
+ {
+ if (tag->type != MULTIBOOT_TAG_TYPE_FRAMEBUFFER)
+ {
+ continue;
+ }
+ fb_tag = (struct multiboot_tag_framebuffer *)tag;
+ break;
+ }
+ KASSERT(fb_tag);
+
+ fb = (uint32_t *)(PHYS_OFFSET + fb_tag->common.framebuffer_addr);
+ fb_width = fb_tag->common.framebuffer_width;
+ fb_height = fb_tag->common.framebuffer_height;
+ fb_pitch = fb_tag->common.framebuffer_pitch;
+ KASSERT(fb_pitch == fb_width * sizeof(uint32_t));
+ KASSERT(fb_tag->common.framebuffer_bpp == 32);
+ KASSERT(fb_tag->common.framebuffer_type == 1);
+ KASSERT(fb_tag->framebuffer_red_field_position == 0x10);
+ KASSERT(fb_tag->framebuffer_green_field_position == 0x08);
+ KASSERT(fb_tag->framebuffer_blue_field_position == 0x00);
+ KASSERT(fb_tag->framebuffer_red_mask_size);
+ KASSERT(fb_tag->framebuffer_green_mask_size == 8);
+ KASSERT(fb_tag->framebuffer_blue_mask_size == 8);
+
+ size_t npages = 0;
+ for (uintptr_t page = (uintptr_t)PAGE_ALIGN_DOWN(fb);
+ page < (uintptr_t)PAGE_ALIGN_UP(fb + fb_width * fb_height);
+ page += PAGE_SIZE)
+ {
+ page_mark_reserved((void *)(page - PHYS_OFFSET));
+ npages++;
+ }
+
+ struct multiboot_tag_vbe *vbe_info = NULL;
+ for (struct multiboot_tag *tag =
+ (struct multiboot_tag *)((uintptr_t)(mb_tag + 1) + PHYS_OFFSET);
+ tag->type != MULTIBOOT_TAG_TYPE_END; tag += TAG_SIZE(tag->size))
+ {
+ if (tag->type != MULTIBOOT_TAG_TYPE_VBE)
+ {
+ continue;
+ }
+ vbe_info = (struct multiboot_tag_vbe *)tag;
+ break;
+ }
+ KASSERT(vbe_info);
+
+#if DOUBLE_BUFFERING
+ fb_buffer = page_alloc_n(npages);
+ KASSERT(fb_buffer && "couldn't allocate double buffer for screen");
+#else
+ fb_buffer = fb;
+#endif
+ pt_map_range(pt_get(), (uintptr_t)fb - PHYS_OFFSET, (uintptr_t)fb,
+ (uintptr_t)PAGE_ALIGN_UP(fb + fb_width * fb_height),
+ PT_PRESENT | PT_WRITE, PT_PRESENT | PT_WRITE);
+ pt_set(pt_get());
+ for (uint32_t i = 0; i < fb_width * fb_height; i++)
+ fb_buffer[i] = 0x008A2BE2;
+ screen_flush();
+}
+
+inline size_t screen_get_width() { return fb_width; }
+
+inline size_t screen_get_height() { return fb_height; }
+
+inline size_t screen_get_character_width() { return SCREEN_CHARACTER_WIDTH; }
+
+inline size_t screen_get_character_height() { return SCREEN_CHARACTER_HEIGHT; }
+
+inline void screen_draw_string(size_t x, size_t y, const char *s, size_t len,
+ color_t color)
+{
+ uint32_t *pos = fb_buffer + y * fb_width + x;
+ while (len--)
+ {
+ const char c = *s++;
+ if (c < ' ' || c > '~')
+ continue;
+ const unsigned char *bitmap = bitmap_letters[c - ' '];
+
+ size_t bm_row = BITMAP_HEIGHT;
+ while (bm_row--)
+ {
+ unsigned char cols = bitmap[bm_row];
+ *pos = BITWISE_TERNARY(cols & 0x80, color.value, *pos);
+ pos++;
+ *pos = BITWISE_TERNARY(cols & 0x40, color.value, *pos);
+ pos++;
+ *pos = BITWISE_TERNARY(cols & 0x20, color.value, *pos);
+ pos++;
+ *pos = BITWISE_TERNARY(cols & 0x10, color.value, *pos);
+ pos++;
+ *pos = BITWISE_TERNARY(cols & 0x08, color.value, *pos);
+ pos++;
+ *pos = BITWISE_TERNARY(cols & 0x04, color.value, *pos);
+ pos++;
+ *pos = BITWISE_TERNARY(cols & 0x02, color.value, *pos);
+ pos++;
+ *pos = BITWISE_TERNARY(cols & 0x01, color.value, *pos);
+ pos++;
+ pos += fb_width - 8;
+ }
+ pos = pos - fb_width * BITMAP_HEIGHT + SCREEN_CHARACTER_WIDTH;
+ }
+}
+
+inline void screen_draw_horizontal(uint32_t *pos, size_t count, color_t color)
+{
+ // while(count--) *pos++ = color.value;
+ __asm__ volatile("cld; rep stosl;" ::"a"(color.value), "D"(pos), "c"(count)
+ : "cc");
+}
+
+inline void screen_copy_horizontal(uint32_t *from, uint32_t *to, size_t count)
+{
+ __asm__ volatile("cld; rep movsl;" ::"S"(from), "D"(to), "c"(count)
+ : "cc");
+}
+
+inline void screen_draw_rect(size_t x, size_t y, size_t width, size_t height,
+ color_t color)
+{
+ uint32_t *top = fb_buffer + y * fb_width + x;
+ screen_draw_horizontal(top, width, color);
+ screen_draw_horizontal(top + height * fb_width, width, color);
+ while (height--)
+ {
+ *top = *(top + width) = color.value;
+ top += fb_width;
+ }
+}
+
+inline void screen_fill(color_t color)
+{
+ __asm__ volatile("cld; rep stosl;" ::"a"(color.value), "D"(fb_buffer),
+ "c"(fb_width * fb_height)
+ : "cc");
+}
+
+inline void screen_fill_rect(size_t x, size_t y, size_t width, size_t height,
+ color_t color)
+{
+ uint32_t *top = fb_buffer + y * fb_width + x;
+ while (height--)
+ {
+ screen_draw_horizontal(top, width, color);
+ top += fb_width;
+ }
+}
+
+inline void screen_copy_rect(size_t fromx, size_t fromy, size_t width,
+ size_t height, size_t tox, size_t toy)
+{
+ uint32_t *from = fb_buffer + fromy * fb_width + fromx;
+ uint32_t *to = fb_buffer + toy * fb_width + tox;
+ while (height--)
+ {
+ screen_copy_horizontal(from, to, width);
+ from += fb_width;
+ to += fb_width;
+ }
+}
+
+inline void screen_flush()
+{
+#if DOUBLE_BUFFERING
+ __asm__ volatile("cld; rep movsl;" ::"S"(fb_buffer), "D"(fb),
+ "c"(fb_width * fb_height)
+ : "cc");
+#endif
+}
+
+static char *shutdown_message = "Weenix has halted cleanly!";
+void screen_print_shutdown()
+{
+ color_t background = {.value = 0x00000000};
+ color_t foreground = {.value = 0x00FFFFFF};
+ screen_fill(background);
+ size_t str_len = strlen(shutdown_message);
+ size_t str_width = str_len * screen_get_character_width();
+ size_t str_height = screen_get_character_height();
+ screen_draw_string((screen_get_width() - str_width) >> 1,
+ (screen_get_height() - str_height) >> 1,
+ shutdown_message, str_len, foreground);
+}
+
+#else
+
+#include "config.h"
+#include "drivers/screen.h"
+#include "main/io.h"
+
+/* Port addresses for the CRT controller */
+#define CRT_CONTROL_ADDR 0x3d4
+#define CRT_CONTROL_DATA 0x3d5
+
+/* Addresses we can pass to the CRT_CONTROLL_ADDR port */
+#define CURSOR_HIGH 0x0e
+#define CURSOR_LOW 0x0f
+
+static uintptr_t vga_textbuffer_phys = 0xB8000;
+static uint16_t *vga_textbuffer;
+static uint16_t vga_blank_screen[VGA_HEIGHT][VGA_WIDTH];
+uint16_t vga_blank_row[VGA_WIDTH];
+
+void vga_enable_cursor()
+{
+ outb(0x3D4, 0x0A);
+ outb(0x3D5, (inb(0x3D5) & 0xC0) | 0);
+
+ outb(0x3D4, 0x0B);
+ outb(0x3D5, (inb(0x3D5) & 0xE0) | 15);
+}
+
+void vga_disable_cursor()
+{
+ outb(0x3D4, 0x0A);
+ outb(0x3D5, 0x20);
+}
+
+void vga_init()
+{
+ /* map the VGA textbuffer (vaddr) to the VGA textbuffer physical address */
+ size_t pages =
+ ADDR_TO_PN(PAGE_ALIGN_UP((uintptr_t)sizeof(vga_blank_screen)));
+ vga_textbuffer = page_alloc_n(pages);
+ KASSERT(vga_textbuffer);
+
+ pt_map_range(pt_get(), (uintptr_t)vga_textbuffer_phys,
+ (uintptr_t)vga_textbuffer,
+ (uintptr_t)vga_textbuffer + ((uintptr_t)PN_TO_ADDR(pages)),
+ PT_PRESENT | PT_WRITE, PT_PRESENT | PT_WRITE);
+ pt_set(pt_get());
+
+ for (size_t i = 0; i < VGA_WIDTH; i++)
+ {
+ vga_blank_row[i] = (VGA_DEFAULT_ATTRIB << 8) | ' ';
+ }
+ for (size_t i = 0; i < VGA_HEIGHT; i++)
+ {
+ memcpy(&vga_blank_screen[i], vga_blank_row, VGA_LINE_SIZE);
+ }
+
+ vga_enable_cursor();
+ vga_clear_screen();
+}
+
+void vga_set_cursor(size_t row, size_t col)
+{
+ uint16_t pos = (row * VGA_WIDTH) + col;
+ outb(0x3D4, 0x0F);
+ outb(0x3D5, (uint8_t)(pos & 0xFF));
+ outb(0x3D4, 0x0E);
+ outb(0x3D5, (uint8_t)((pos >> 8) & 0xFF));
+}
+
+void vga_clear_screen()
+{
+ memcpy(vga_textbuffer, vga_blank_screen, sizeof(vga_blank_screen));
+}
+
+void vga_write_char_at(size_t row, size_t col, uint16_t v)
+{
+ KASSERT(row < VGA_HEIGHT && col < VGA_WIDTH);
+ vga_textbuffer[(row * VGA_WIDTH) + col] = v;
+}
+
+static char *shutdown_message = "Weenix has halted cleanly!";
+void screen_print_shutdown()
+{
+ vga_disable_cursor();
+ vga_clear_screen();
+ int x = (VGA_WIDTH - strlen(shutdown_message)) / 2;
+ int y = VGA_HEIGHT / 2;
+
+ for (size_t i = 0; i < strlen(shutdown_message); i++)
+ {
+ vga_write_char_at(y, x + i,
+ (VGA_DEFAULT_ATTRIB << 8) | shutdown_message[i]);
+ }
+}
+
+#endif \ No newline at end of file
diff --git a/kernel/drivers/tty/ldisc.c b/kernel/drivers/tty/ldisc.c
new file mode 100644
index 0000000..d1044f2
--- /dev/null
+++ b/kernel/drivers/tty/ldisc.c
@@ -0,0 +1,120 @@
+#include "drivers/tty/ldisc.h"
+#include <drivers/keyboard.h>
+#include <drivers/tty/tty.h>
+#include <errno.h>
+#include <util/bits.h>
+#include <util/debug.h>
+#include <util/string.h>
+
+#define ldisc_to_tty(ldisc) CONTAINER_OF((ldisc), tty_t, tty_ldisc)
+
+/**
+ * Initialize the line discipline. Don't forget to wipe the buffer associated
+ * with the line discipline clean.
+ *
+ * @param ldisc line discipline.
+ */
+void ldisc_init(ldisc_t *ldisc)
+{
+ NOT_YET_IMPLEMENTED("DRIVERS: ***none***");
+}
+
+/**
+ * While there are no new characters to be read from the line discipline's
+ * buffer, you should make the current thread to sleep on the line discipline's
+ * read queue. Note that this sleep can be cancelled. What conditions must be met
+ * for there to be no characters to be read?
+ *
+ * @param ldisc the line discipline
+ * @param lock the lock associated with `ldisc`
+ * @return 0 if there are new characters to be read or the ldisc is full.
+ * If the sleep was interrupted, return what
+ * `sched_cancellable_sleep_on` returned (i.e. -EINTR)
+ */
+long ldisc_wait_read(ldisc_t *ldisc)
+{
+ NOT_YET_IMPLEMENTED("DRIVERS: ***none***");
+ return -1;
+}
+
+/**
+ * Reads `count` bytes (at max) from the line discipline's buffer into the
+ * provided buffer. Keep in mind the the ldisc's buffer is circular.
+ *
+ * If you encounter a new line symbol before you have read `count` bytes, you
+ * should stop copying and return the bytes read until now.
+ *
+ * If you encounter an `EOT` you should stop reading and you should NOT include
+ * the `EOT` in the count of the number of bytes read
+ *
+ * @param ldisc the line discipline
+ * @param buf the buffer to read into.
+ * @param count the maximum number of bytes to read from ldisc.
+ * @return the number of bytes read from the ldisc.
+ */
+size_t ldisc_read(ldisc_t *ldisc, char *buf, size_t count)
+{
+ NOT_YET_IMPLEMENTED("DRIVERS: ***none***");
+ return 0;
+}
+
+/**
+ * Place the character received into the ldisc's buffer. You should also update
+ * relevant fields of the struct.
+ *
+ * An easier way of handling new characters is making sure that you always have
+ * one byte left in the line discipline. This way, if the new character you
+ * received is a new line symbol (user hit enter), you can still place the new
+ * line symbol into the buffer; if the new character is not a new line symbol,
+ * you shouldn't place it into the buffer so that you can leave the space for
+ * a new line symbol in the future.
+ *
+ * If the line discipline is full, all incoming characters should be ignored.
+ *
+ * Here are some special cases to consider:
+ * 1. If the character is a backspace:
+ * * if there is a character to remove you must also emit a `\b` to
+ * the vterminal.
+ * 2. If the character is end of transmission (EOT) character (typing ctrl-d)
+ * 3. If the character is end of text (ETX) character (typing ctrl-c)
+ * 4. If your buffer is almost full and what you received is not a new line
+ * symbol
+ *
+ * If you did receive a new line symbol, you should wake up the thread that is
+ * sleeping on the wait queue of the line discipline. You should also
+ * emit a `\n` to the vterminal by using `vterminal_write`.
+ *
+ * If you encounter the `EOT` character, you should add it to the buffer,
+ * cook the buffer, and wake up the reader (but do not emit an `\n` character
+ * to the vterminal)
+ *
+ * In case of `ETX` you should cause the input line to be effectively transformed
+ * into a cooked blank line. You should clear uncooked portion of the line, by
+ * adjusting ldisc_head.
+ *
+ * Finally, if the none of the above cases apply you should fallback to
+ * `vterminal_key_pressed`.
+ *
+ * Don't forget to write the corresponding characters to the virtual terminal
+ * when it applies!
+ *
+ * @param ldisc the line discipline
+ * @param c the new character
+ */
+void ldisc_key_pressed(ldisc_t *ldisc, char c)
+{
+ NOT_YET_IMPLEMENTED("DRIVERS: ***none***");
+}
+
+/**
+ * Copy the raw part of the line discipline buffer into the buffer provided.
+ *
+ * @param ldisc the line discipline
+ * @param s the character buffer to write to
+ * @return the number of bytes copied
+ */
+size_t ldisc_get_current_line_raw(ldisc_t *ldisc, char *s)
+{
+ NOT_YET_IMPLEMENTED("DRIVERS: ***none***");
+ return 0;
+}
diff --git a/kernel/drivers/tty/tty.c b/kernel/drivers/tty/tty.c
new file mode 100644
index 0000000..a08df13
--- /dev/null
+++ b/kernel/drivers/tty/tty.c
@@ -0,0 +1,135 @@
+#include "drivers/tty/tty.h"
+#include "drivers/chardev.h"
+#include "drivers/dev.h"
+#include "drivers/keyboard.h"
+#include "kernel.h"
+#include "mm/kmalloc.h"
+#include "util/debug.h"
+#include <errno.h>
+
+#ifndef NTERMS
+#define NTERMS 3
+#endif
+
+ssize_t tty_read(chardev_t *cdev, size_t pos, void *buf, size_t count);
+ssize_t tty_write(chardev_t *cdev, size_t pos, const void *buf, size_t count);
+
+chardev_ops_t tty_cdev_ops = {.read = tty_read,
+ .write = tty_write,
+ .mmap = NULL,
+ .fill_pframe = NULL,
+ .flush_pframe = NULL};
+
+tty_t *ttys[NTERMS] = {NULL};
+
+size_t active_tty;
+
+static void tty_receive_char_multiplexer(uint8_t c);
+
+void tty_init()
+{
+ for (unsigned i = 0; i < NTERMS; i++)
+ {
+ tty_t *tty = ttys[i] = kmalloc(sizeof(tty_t));
+ vterminal_init(&tty->tty_vterminal);
+ ldisc_init(&tty->tty_ldisc);
+
+ tty->tty_cdev.cd_id = MKDEVID(TTY_MAJOR, i);
+ list_link_init(&tty->tty_cdev.cd_link);
+ tty->tty_cdev.cd_ops = &tty_cdev_ops;
+
+ kmutex_init(&tty->tty_write_mutex);
+ kmutex_init(&tty->tty_read_mutex);
+
+ long ret = chardev_register(&tty->tty_cdev);
+ KASSERT(!ret);
+ }
+ active_tty = 0;
+ vterminal_make_active(&ttys[active_tty]->tty_vterminal);
+ KASSERT(ttys[active_tty]);
+
+ keyboard_init(tty_receive_char_multiplexer);
+}
+
+/**
+ * Reads from the tty to the buffer.
+ *
+ * You should first lock the read mutex of the tty. You should
+ * then wait until there is something in the line discipline's buffer and only
+ * read from the ldisc's buffer if there are new characters.
+ *
+ * To prevent being preempted, you should set IPL using INTR_KEYBOARD
+ * correctly and revert it once you are done.
+ *
+ * @param cdev the character device that represents tty
+ * @param pos the position to start reading from; should be ignored
+ * @param buf the buffer to read into
+ * @param count the maximum number of bytes to read
+ * @return the number of bytes actually read into the buffer
+ */
+ssize_t tty_read(chardev_t *cdev, size_t pos, void *buf, size_t count)
+{
+ NOT_YET_IMPLEMENTED("DRIVERS: ***none***");
+ return -1;
+}
+
+/**
+ * Writes to the tty from the buffer.
+ *
+ * You should first lock the write mutex of the tty. Then you can use
+ * `vterminal_write` to write to the terminal. Don't forget to use IPL to
+ * guard this from preemption!
+ *
+ * @param cdev the character device that represents tty
+ * @param pos the position to start reading from; should be ignored
+ * @param buf the buffer to read from
+ * @param count the maximum number of bytes to write to the terminal
+ * @return the number of bytes actually written
+ */
+ssize_t tty_write(chardev_t *cdev, size_t pos, const void *buf, size_t count)
+{
+ NOT_YET_IMPLEMENTED("DRIVERS: ***none***");
+ return -1;
+}
+
+static void tty_receive_char_multiplexer(uint8_t c)
+{
+ tty_t *tty = ttys[active_tty];
+
+ if (c >= F1 && c <= F12)
+ {
+ if (c - F1 < NTERMS)
+ {
+ /* TODO: this is totally unsafe... Fix it */
+ active_tty = (unsigned)c - F1;
+ tty = ttys[active_tty];
+ vterminal_make_active(&tty->tty_vterminal);
+ }
+ return;
+ }
+ if (c == CR)
+ c = LF;
+ else if (c == DEL)
+ c = BS;
+
+ vterminal_t *vt = &tty->tty_vterminal;
+ switch ((unsigned)c)
+ {
+ case SCROLL_DOWN:
+ case SCROLL_UP:
+ // vterminal_scroll(vt, c == SCROLL_DOWN ? 1 : -1);
+ break;
+ case SCROLL_DOWN_PAGE:
+ case SCROLL_UP_PAGE:
+ // vterminal_scroll(vt, c == SCROLL_DOWN_PAGE ? vt->vt_height :
+ // -vt->vt_height);
+ break;
+ case ESC:
+ // vterminal_scroll_to_bottom(vt);
+ break;
+ default:
+ ldisc_key_pressed(&tty->tty_ldisc, c);
+ // vterminal_key_pressed(vt);
+ break;
+ }
+}
diff --git a/kernel/drivers/tty/vterminal.c b/kernel/drivers/tty/vterminal.c
new file mode 100644
index 0000000..9ac3421
--- /dev/null
+++ b/kernel/drivers/tty/vterminal.c
@@ -0,0 +1,1384 @@
+#include <drivers/keyboard.h>
+#include <drivers/tty/ldisc.h>
+#include <drivers/tty/tty.h>
+#include <drivers/tty/vterminal.h>
+#include <errno.h>
+#include <mm/kmalloc.h>
+#include <util/debug.h>
+#include <util/string.h>
+
+/*
+
+vterminal.c is used to manage the display of the terminal screen, this includes
+printing the keys pressed, output of the command passed, managing the cursor
+position, etc.
+
+vterminal_write is called by functions in tty.c and ldisc.c, namely tty_write
+and ldisc_key_pressed. vterminal_write then calls vtconsole_write which takes
+care of the processing of the characters with the help of vtconsole_process
+vtconsole_process and vtconsole_append are responsible for printing the
+characters corresponding to the keys pressed onto the console.
+
+vtconsole_append also manages the position of the cursor while the uncooked
+part of the buffer is being printed. There are mutltiple other functions defined
+in this file which help in displaying the cursor on the console. The console
+also supports scrolling which is handled by vtconsole_scroll. vterminal_clear
+is used to clear the content of the console.
+
+The functions, vterminal_make_active, vterminal_init, vtconsole, paint_callback
+and cursor_move_callback are responsible for carrying out the necessary
+initialization and initial display of the console.
+
+*/
+
+#define vterminal_to_tty(vterminal) \
+ CONTAINER_OF((vterminal), tty_t, tty_vterminal)
+
+#ifdef __VGABUF___
+
+/*
+Without turning on VGABUF, the terminal is treated as a simple device: one sent characters
+to it to be displayed. It did the right thing with new lines and with backspaces,
+but didn't handle any other control characters. The VGA handles all sorts of other things,
+but we also have to explicitly tell it to scroll. VGABUF allows Weenix to toggle between
+VGA text mode (that understands text) and VGA buffer mode (that is pixel based).
+*/
+
+#define VT_LINE_POSITION(vt, line) \
+ ((vt)->vt_line_positions[((vt)->vt_line_offset + (vt)->vt_height + \
+ (line)) % \
+ (vt)->vt_height])
+
+#define vterminal_to_tty(vterminal) \
+ CONTAINER_OF((vterminal), tty_t, tty_vterminal)
+
+#define VT_OFFSCREEN ((size_t)-1)
+
+static long vterminal_add_chunk(vterminal_t *vt);
+
+static vterminal_t *active_vt = NULL;
+
+void vterminal_init(vterminal_t *vt)
+{
+ vt->vt_width = screen_get_width() / screen_get_character_width();
+ vt->vt_height = screen_get_height() / screen_get_character_height();
+ list_init(&vt->vt_history_chunks);
+ vt->vt_line_positions = kmalloc(sizeof(size_t) * vt->vt_height * 2);
+ KASSERT(vt->vt_line_positions);
+ vt->vt_line_widths = vt->vt_line_positions + vt->vt_height;
+
+ list_init(&vt->vt_history_chunks);
+ long success = vterminal_add_chunk(vt);
+ KASSERT(success && !list_empty(&vt->vt_history_chunks));
+
+ vterminal_clear(vt);
+}
+
+static void vterminal_seek_to_pos(vterminal_t *vt, size_t pos,
+ vterminal_history_chunk_t **chunk,
+ size_t *offset)
+{
+ if (pos > vt->vt_len)
+ {
+ *chunk = NULL;
+ *offset = 0;
+ return;
+ }
+ *offset = pos % VT_CHARS_PER_HISTORY_CHUNK;
+ size_t n_chunks = vt->vt_len / VT_CHARS_PER_HISTORY_CHUNK;
+ size_t iterations = pos / VT_CHARS_PER_HISTORY_CHUNK;
+ if (iterations > n_chunks >> 1)
+ {
+ iterations = n_chunks - iterations;
+ list_iterate_reverse(&vt->vt_history_chunks, chunk_iter,
+ vterminal_history_chunk_t, link)
+ {
+ if (!iterations--)
+ {
+ *chunk = chunk_iter;
+ return;
+ }
+ }
+ }
+ else
+ {
+ list_iterate(&vt->vt_history_chunks, chunk_iter,
+ vterminal_history_chunk_t, link)
+ {
+ if (!iterations--)
+ {
+ *chunk = chunk_iter;
+ return;
+ }
+ }
+ }
+}
+
+static inline long vterminal_seek_to_offset(vterminal_t *vt,
+ vterminal_history_chunk_t **chunk,
+ size_t *offset)
+{
+ while (*offset >= VT_CHARS_PER_HISTORY_CHUNK)
+ {
+ if (*chunk ==
+ list_tail(&vt->vt_history_chunks, vterminal_history_chunk_t, link))
+ return 0;
+ *chunk = list_next(*chunk, vterminal_history_chunk_t, link);
+ *offset -= VT_CHARS_PER_HISTORY_CHUNK;
+ }
+ return 1;
+}
+
+size_t vterminal_calculate_line_width_forward(vterminal_t *vt, size_t pos)
+{
+ vterminal_history_chunk_t *chunk;
+ size_t offset;
+ vterminal_seek_to_pos(vt, pos, &chunk, &offset);
+ if (!chunk)
+ return 0;
+ size_t width = 0;
+ while (pos + width < vt->vt_len && chunk->chars[offset++] != LF)
+ {
+ width++;
+ if (!vterminal_seek_to_offset(vt, &chunk, &offset))
+ break;
+ }
+ return width;
+}
+static void vterminal_redraw_lines(vterminal_t *vt, size_t start, size_t end)
+{
+ KASSERT(start < vt->vt_height && start < end && end <= vt->vt_height);
+
+ size_t pos = VT_LINE_POSITION(vt, start);
+ vterminal_history_chunk_t *chunk;
+ size_t offset;
+ vterminal_seek_to_pos(vt, pos, &chunk, &offset);
+
+ color_t cursor = {.value = 0x00D3D3D3};
+ color_t background = {.value = 0x00000000};
+ color_t foreground = {.value = 0x00FFFFFF};
+
+ size_t screen_y = screen_get_character_height() * start;
+
+ size_t line = start;
+ while (line < end && pos <= vt->vt_len &&
+ vterminal_seek_to_offset(vt, &chunk, &offset))
+ {
+ KASSERT(pos == VT_LINE_POSITION(vt, line));
+
+ size_t cur_width = vt->vt_line_widths[line];
+ size_t new_width, next_pos;
+ if (line + 1 < vt->vt_height &&
+ (next_pos = VT_LINE_POSITION(vt, line + 1)) != VT_OFFSCREEN)
+ {
+ new_width = next_pos - pos - 1;
+ }
+ else
+ {
+ new_width = vterminal_calculate_line_width_forward(vt, pos);
+ }
+ vt->vt_line_widths[line] = new_width;
+
+ screen_fill_rect(
+ 0, screen_y,
+ MAX(cur_width, new_width) * screen_get_character_width(),
+ screen_get_character_height(), background);
+ if (pos <= vt->vt_cursor_pos && vt->vt_cursor_pos <= pos + new_width)
+ {
+ screen_fill_rect(
+ (vt->vt_cursor_pos - pos) * screen_get_character_width(),
+ screen_y, screen_get_character_width(),
+ screen_get_character_height(), cursor);
+ vt->vt_line_widths[line]++;
+ }
+ size_t drawn = 0;
+ while (drawn != new_width)
+ {
+ size_t to_draw =
+ MIN(VT_CHARS_PER_HISTORY_CHUNK - offset, new_width - drawn);
+ screen_draw_string(drawn * screen_get_character_width(), screen_y,
+ chunk->chars + offset, to_draw, foreground);
+ drawn += to_draw;
+ offset += to_draw;
+ if (!vterminal_seek_to_offset(vt, &chunk, &offset))
+ {
+ vterminal_seek_to_offset(vt, &chunk, &offset);
+ KASSERT(drawn == new_width);
+ break;
+ }
+ }
+
+ pos += new_width + 1;
+ KASSERT(chunk->chars[offset] == LF || pos >= vt->vt_len);
+
+ offset++;
+ line++;
+ screen_y += screen_get_character_height();
+ }
+ while (line < end)
+ {
+ // dbg(DBG_TEMP, "clearing line %lu\n", line);
+ screen_fill_rect(
+ 0, screen_y,
+ vt->vt_line_widths[line] * screen_get_character_width(),
+ screen_get_character_height(), background);
+ vt->vt_line_widths[line] = 0;
+ line++;
+ screen_y += screen_get_character_height();
+ }
+}
+
+void vterminal_make_active(vterminal_t *vt)
+{
+ KASSERT(vt);
+ if (active_vt == vt)
+ return;
+ active_vt = vt;
+ for (size_t line = 0; line < vt->vt_height; line++)
+ {
+ vt->vt_line_widths[line] = vt->vt_width;
+ }
+ color_t background = {.value = 0x00000000};
+ screen_fill_rect(
+ vt->vt_width * screen_get_character_width(), 0,
+ screen_get_width() - vt->vt_width * screen_get_character_width(),
+ screen_get_height(), background);
+ screen_fill_rect(
+ 0, vt->vt_height * screen_get_character_height(), screen_get_width(),
+ screen_get_height() - vt->vt_height * screen_get_character_height(),
+ background);
+ vterminal_redraw_lines(vt, 0, vt->vt_height);
+}
+
+size_t vterminal_calculate_line_width_backward(vterminal_t *vt, size_t pos)
+{
+ if (!pos)
+ return 0;
+ vterminal_history_chunk_t *chunk;
+ size_t offset;
+ vterminal_seek_to_pos(vt, pos - 1, &chunk, &offset);
+ size_t width = 0;
+ while (chunk->chars[offset] != LF)
+ {
+ width++;
+ if (offset == 0)
+ {
+ if (chunk == list_head(&vt->vt_history_chunks,
+ vterminal_history_chunk_t, link))
+ break;
+ chunk = list_prev(chunk, vterminal_history_chunk_t, link);
+ offset = VT_CHARS_PER_HISTORY_CHUNK;
+ }
+ offset--;
+ }
+ return width;
+}
+
+static inline void vterminal_get_last_visible_line_information(vterminal_t *vt,
+ size_t *position,
+ size_t *width)
+{
+ for (long line = vt->vt_height - 1; line >= 0; line--)
+ {
+ if (VT_LINE_POSITION(vt, line) != VT_OFFSCREEN)
+ {
+ *position = VT_LINE_POSITION(vt, line);
+ *width = vterminal_calculate_line_width_forward(vt, *position);
+ return;
+ }
+ }
+ panic("should always find last visible line information");
+}
+
+static inline long vterminal_scrolled_to_bottom(vterminal_t *vt)
+{
+ size_t position;
+ size_t width;
+ vterminal_get_last_visible_line_information(vt, &position, &width);
+ return position + width == vt->vt_len;
+}
+
+void vterminal_scroll_to_bottom(vterminal_t *vt)
+{
+ if (vterminal_scrolled_to_bottom(vt))
+ return;
+ vt->vt_line_offset = 0;
+ VT_LINE_POSITION(vt, 0) = vt->vt_len + 1;
+ vterminal_scroll(vt, -vt->vt_height);
+ for (size_t line = vt->vt_height - vt->vt_line_offset; line < vt->vt_height;
+ line++)
+ {
+ VT_LINE_POSITION(vt, line) = VT_OFFSCREEN;
+ }
+}
+
+void vterminal_scroll_draw(vterminal_t *vt, long count)
+{
+ if (count > 0)
+ {
+ if ((size_t)count > vt->vt_height)
+ count = vt->vt_height;
+ size_t copy_distance = count * screen_get_character_height();
+ size_t screen_y = 0;
+ for (size_t line = 0; line < vt->vt_height - count; line++)
+ {
+ screen_copy_rect(0, screen_y + copy_distance,
+ MAX(vt->vt_line_widths[line],
+ vt->vt_line_widths[line + count]) *
+ screen_get_character_width(),
+ screen_get_character_height(), 0, screen_y);
+ vt->vt_line_widths[line] = vt->vt_line_widths[line + count];
+ screen_y += screen_get_character_height();
+ }
+ vterminal_redraw_lines(vt, vt->vt_height - count, vt->vt_height);
+ }
+ else if (count < 0)
+ {
+ count *= -1;
+ if ((size_t)count > vt->vt_height)
+ count = vt->vt_height;
+ size_t copy_distance = count * screen_get_character_height();
+ size_t screen_y =
+ (vt->vt_height - count) * screen_get_character_height();
+ for (size_t line = vt->vt_height - count; line >= (size_t)count;
+ line--)
+ {
+ screen_copy_rect(0, screen_y - copy_distance,
+ MAX(vt->vt_line_widths[line],
+ vt->vt_line_widths[line - count]) *
+ screen_get_character_width(),
+ screen_get_character_height(), 0, screen_y);
+ vt->vt_line_widths[line] = vt->vt_line_widths[line - count];
+ screen_y -= screen_get_character_height();
+ }
+ vterminal_redraw_lines(vt, 0, (size_t)count);
+ }
+}
+
+void vterminal_scroll(vterminal_t *vt, long count)
+{
+ long n_scrolls = 0;
+ if (count < 0)
+ {
+ size_t first_line_position = VT_LINE_POSITION(vt, 0);
+ while (count++ && first_line_position)
+ {
+ size_t width = vterminal_calculate_line_width_backward(
+ vt, first_line_position - 1);
+ size_t top_line_position = first_line_position - width - 1;
+ VT_LINE_POSITION(vt, -1) = top_line_position;
+ if (!vt->vt_line_offset)
+ vt->vt_line_offset = vt->vt_height;
+ vt->vt_line_offset--;
+ n_scrolls++;
+ first_line_position = top_line_position;
+ }
+ if (n_scrolls)
+ {
+ vterminal_scroll_draw(vt, -n_scrolls);
+ }
+ }
+ else if (count > 0)
+ {
+ size_t last_line_position;
+ size_t last_line_width;
+ vterminal_get_last_visible_line_information(vt, &last_line_position,
+ &last_line_width);
+ while (count-- && last_line_position + last_line_width < vt->vt_len)
+ {
+ size_t bottom_line_position =
+ last_line_position + last_line_width + 1;
+ VT_LINE_POSITION(vt, 0) = bottom_line_position;
+ vt->vt_line_offset++;
+ if ((unsigned)vt->vt_line_offset == vt->vt_height)
+ vt->vt_line_offset = 0;
+ n_scrolls++;
+ last_line_position = bottom_line_position;
+ last_line_width =
+ vterminal_calculate_line_width_forward(vt, last_line_position);
+ }
+ if (n_scrolls)
+ {
+ vterminal_scroll_draw(vt, n_scrolls);
+ }
+ }
+}
+
+void vterminal_clear(vterminal_t *vt)
+{
+ list_iterate(&vt->vt_history_chunks, chunk, vterminal_history_chunk_t,
+ link)
+ {
+ if (chunk != list_tail(&vt->vt_history_chunks,
+ vterminal_history_chunk_t, link))
+ {
+ list_remove(&chunk->link);
+ page_free_n(chunk, VT_PAGES_PER_HISTORY_CHUNK);
+ }
+ else
+ {
+ memset(chunk, 0, VT_CHARS_PER_HISTORY_CHUNK);
+ }
+ }
+ vt->vt_len = 0;
+ for (size_t i = 0; i < vt->vt_height; i++)
+ {
+ vt->vt_line_widths[i] = 0;
+ vt->vt_line_positions[i] = VT_OFFSCREEN;
+ }
+ vt->vt_line_offset = 0;
+ vt->vt_cursor_pos = 0;
+ vt->vt_input_pos = 0;
+ VT_LINE_POSITION(vt, 0) = 0;
+}
+
+static long vterminal_add_chunk(vterminal_t *vt)
+{
+ vterminal_history_chunk_t *chunk = page_alloc_n(VT_PAGES_PER_HISTORY_CHUNK);
+ if (!chunk)
+ {
+ chunk =
+ list_head(&vt->vt_history_chunks, vterminal_history_chunk_t, link);
+ if (chunk ==
+ list_tail(&vt->vt_history_chunks, vterminal_history_chunk_t, link))
+ return 0;
+ list_remove(&chunk->link);
+
+ // TODO what if the first chunk that we're removing is visible? lol
+ for (size_t i = 0; i < vt->vt_height; i++)
+ {
+ KASSERT(vt->vt_line_positions[i] >= VT_CHARS_PER_HISTORY_CHUNK &&
+ "NYI");
+ vt->vt_line_positions[i] -= VT_CHARS_PER_HISTORY_CHUNK;
+ }
+ KASSERT(vt->vt_input_pos >= VT_CHARS_PER_HISTORY_CHUNK &&
+ vt->vt_cursor_pos >= VT_CHARS_PER_HISTORY_CHUNK &&
+ vt->vt_len >= VT_CHARS_PER_HISTORY_CHUNK && "NYI");
+ vt->vt_input_pos -= VT_CHARS_PER_HISTORY_CHUNK;
+ vt->vt_cursor_pos -= VT_CHARS_PER_HISTORY_CHUNK;
+ vt->vt_len -= VT_CHARS_PER_HISTORY_CHUNK;
+ }
+
+ memset(chunk, 0, sizeof(vterminal_history_chunk_t));
+
+ list_link_init(&chunk->link);
+ list_insert_tail(&vt->vt_history_chunks, &chunk->link);
+
+ return 1;
+}
+
+static inline long vterminal_allocate_to_offset(
+ vterminal_t *vt, vterminal_history_chunk_t **chunk, size_t *offset)
+{
+ if (!vterminal_seek_to_offset(vt, chunk, offset))
+ {
+ if (!vterminal_add_chunk(vt))
+ {
+ return 0;
+ }
+ return vterminal_seek_to_offset(vt, chunk, offset);
+ }
+ return 1;
+}
+
+size_t vterminal_write(vterminal_t *vt, const char *buf, size_t len)
+{
+ size_t written = 0;
+
+ size_t last_line_width =
+ vterminal_calculate_line_width_backward(vt, vt->vt_len);
+ size_t last_line_idx;
+ size_t last_line_position = VT_OFFSCREEN;
+ for (last_line_idx = vt->vt_height - 1;; last_line_idx--)
+ {
+ if ((last_line_position = VT_LINE_POSITION(vt, last_line_idx)) !=
+ VT_OFFSCREEN)
+ {
+ break;
+ }
+ }
+ KASSERT(last_line_idx < vt->vt_height);
+
+ vterminal_history_chunk_t *chunk;
+ size_t offset;
+ vterminal_seek_to_pos(vt, vt->vt_len, &chunk, &offset);
+
+ size_t last_line_idx_initial = (size_t)last_line_idx;
+
+ long need_to_scroll = last_line_position + last_line_width == vt->vt_len;
+ size_t n_scroll_downs = 0;
+ while (len--)
+ {
+ char c = *(buf++);
+ written++;
+ if (c != LF)
+ {
+ chunk->chars[offset++] = c;
+ vt->vt_len++;
+ last_line_width++;
+ if (!vterminal_allocate_to_offset(vt, &chunk, &offset))
+ goto done;
+ }
+ if (last_line_width == vt->vt_width)
+ {
+ c = LF;
+ }
+ if (c == LF)
+ {
+ chunk->chars[offset++] = LF;
+ vt->vt_len++;
+ if (!vterminal_allocate_to_offset(vt, &chunk, &offset))
+ goto done;
+
+ if (need_to_scroll)
+ {
+ KASSERT(last_line_position + last_line_width + 1 == vt->vt_len);
+ if (last_line_idx == vt->vt_height - 1)
+ {
+ vt->vt_line_offset++;
+ n_scroll_downs++;
+ if ((unsigned)vt->vt_line_offset == vt->vt_height)
+ vt->vt_line_offset = 0;
+ if (last_line_idx_initial)
+ last_line_idx_initial--;
+ }
+ else
+ {
+ last_line_idx++;
+ }
+ last_line_width = 0;
+ last_line_position = VT_LINE_POSITION(vt, last_line_idx) =
+ vt->vt_len;
+ }
+ }
+ }
+
+ last_line_idx++;
+done:
+ vt->vt_input_pos = vt->vt_len;
+ vt->vt_cursor_pos = vt->vt_len;
+
+ if (need_to_scroll)
+ {
+ if (active_vt == vt)
+ {
+ if (last_line_idx >= vt->vt_height &&
+ n_scroll_downs < vt->vt_height)
+ {
+ vterminal_scroll_draw(vt, n_scroll_downs);
+ last_line_idx = vt->vt_height;
+ }
+ vterminal_redraw_lines(vt, last_line_idx_initial,
+ MIN(last_line_idx, vt->vt_height));
+ }
+ else
+ {
+ vterminal_scroll(vt, n_scroll_downs);
+ }
+ }
+ return written;
+}
+
+static void vterminal_free_from_position_to_end(vterminal_t *vt, size_t pos)
+{
+ vterminal_history_chunk_t *chunk;
+ size_t offset;
+ vterminal_seek_to_pos(vt, vt->vt_input_pos, &chunk, &offset);
+ while (chunk !=
+ list_tail(&vt->vt_history_chunks, vterminal_history_chunk_t, link))
+ {
+ vterminal_history_chunk_t *to_remove =
+ list_tail(&vt->vt_history_chunks, vterminal_history_chunk_t, link);
+ list_remove(&to_remove->link);
+ page_free_n(to_remove, VT_PAGES_PER_HISTORY_CHUNK);
+ }
+ vt->vt_len = pos;
+ for (size_t line = 0; line < vt->vt_height; line++)
+ {
+ if (VT_LINE_POSITION(vt, line) > vt->vt_len)
+ {
+ VT_LINE_POSITION(vt, line) = VT_OFFSCREEN;
+ vterminal_redraw_lines(vt, line, line + 1);
+ }
+ }
+}
+
+void vterminal_key_pressed(vterminal_t *vt)
+{
+ KASSERT(active_vt == vt);
+ vterminal_scroll_to_bottom(vt);
+ char buf[LDISC_BUFFER_SIZE];
+ size_t len =
+ ldisc_get_current_line_raw(&vterminal_to_tty(vt)->tty_ldisc, buf);
+ size_t initial_input_pos = vt->vt_input_pos;
+ vterminal_free_from_position_to_end(vt, initial_input_pos);
+ vterminal_write(vt, buf, len);
+
+ vt->vt_input_pos = initial_input_pos;
+}
+
+#endif
+
+#define VGA_SCREEN_WIDTH 80
+#define VGA_SCREEN_HEIGHT 25
+
+#define VGACOLOR_BLACK 0X0
+#define VGACOLOR_BLUE 0X1
+#define VGACOLOR_GREEN 0X2
+#define VGACOLOR_CYAN 0X3
+#define VGACOLOR_RED 0X4
+#define VGACOLOR_MAGENTA 0X5
+#define VGACOLOR_BROWN 0X6
+#define VGACOLOR_LIGHT_GRAY 0X7
+#define VGACOLOR_GRAY 0X8
+#define VGACOLOR_LIGHT_BLUE 0X9
+#define VGACOLOR_LIGHT_GREEN 0XA
+#define VGACOLOR_LIGHT_CYAN 0XB
+#define VGACOLOR_LIGHT_RED 0XC
+#define VGACOLOR_LIGHT_MAGENTA 0XD
+#define VGACOLOR_LIGHT_YELLOW 0XE
+#define VGACOLOR_WHITE 0XF
+
+/* --- Constructor/Destructor ----------------------------------------------- */
+
+// vtconsole contructor/init function
+vtconsole_t *vtconsole(vtconsole_t *vtc, int width, int height,
+ vtc_paint_handler_t on_paint,
+ vtc_cursor_handler_t on_move)
+{
+ vtc->width = width;
+ vtc->height = height;
+
+ vtansi_parser_t ap;
+ ap.state = VTSTATE_ESC;
+ ap.index = 0;
+ vtansi_arg_t vta[8];
+ memset(ap.stack, 0, sizeof(vtansi_arg_t) * VTC_ANSI_PARSER_STACK_SIZE);
+ // ap.stack = vta;
+ vtc->ansiparser = ap;
+
+ vtc->attr = VTC_DEFAULT_ATTR;
+
+ vtc->buffer = kmalloc(width * height * sizeof(vtcell_t));
+
+ vtc->tabs = kmalloc(LDISC_BUFFER_SIZE * sizeof(int));
+ vtc->tab_index = 0;
+
+ vtc->cursor = (vtcursor_t){0, 0};
+
+ vtc->on_paint = on_paint;
+ vtc->on_move = on_move;
+
+ vtconsole_clear(vtc, 0, 0, width, height - 1);
+
+ return vtc;
+}
+
+// function to free the vtconosle/vterminal buffer
+void vtconsole_delete(vtconsole_t *vtc)
+{
+ kfree(vtc->buffer);
+ kfree(vtc->tabs);
+ kfree(vtc);
+}
+
+/* --- Internal methods ---------------------------------------------------- */
+
+// function to clear everything on the vterminal
+void vtconsole_clear(vtconsole_t *vtc, int fromx, int fromy, int tox, int toy)
+{
+ for (int i = fromx + fromy * vtc->width; i < tox + toy * vtc->width; i++)
+ {
+ vtcell_t *cell = &vtc->buffer[i];
+
+ cell->attr = VTC_DEFAULT_ATTR;
+ cell->c = ' ';
+
+ if (vtc->on_paint)
+ {
+ vtc->on_paint(vtc, cell, i % vtc->width, i / vtc->width);
+ }
+ }
+}
+
+// helper function for vtconsole_newline to scroll down the screen.
+void vtconsole_scroll(vtconsole_t *vtc, int lines)
+{
+ if (lines == 0)
+ return;
+
+ lines = lines > vtc->height ? vtc->height : lines;
+
+ // Scroll the screen by number of $lines.
+ for (int i = 0; i < ((vtc->width * vtc->height) - (vtc->width * lines));
+ i++)
+ {
+ vtc->buffer[i] = vtc->buffer[i + (vtc->width * lines)];
+
+ if (vtc->on_paint)
+ {
+ vtc->on_paint(vtc, &vtc->buffer[i], i % vtc->width, i / vtc->width);
+ }
+ }
+
+ // Clear the last $lines.
+ for (int i = ((vtc->width * vtc->height) - (vtc->width * lines));
+ i < vtc->width * vtc->height; i++)
+ {
+ vtcell_t *cell = &vtc->buffer[i];
+ cell->attr = VTC_DEFAULT_ATTR;
+ cell->c = ' ';
+
+ if (vtc->on_paint)
+ {
+ vtc->on_paint(vtc, &vtc->buffer[i], i % vtc->width, i / vtc->width);
+ }
+ }
+
+ // Move the cursor up $lines
+ if (vtc->cursor.y > 0)
+ {
+ vtc->cursor.y -= lines;
+
+ if (vtc->cursor.y < 0)
+ vtc->cursor.y = 0;
+
+ if (vtc->on_move)
+ {
+ vtc->on_move(vtc, &vtc->cursor);
+ }
+ }
+}
+
+// Append a new line
+void vtconsole_newline(vtconsole_t *vtc)
+{
+ vtc->cursor.x = 0;
+ vtc->cursor.y++;
+
+ if (vtc->cursor.y == vtc->height)
+ {
+ vtconsole_scroll(vtc, 1);
+ }
+
+ if (vtc->on_move)
+ {
+ vtc->on_move(vtc, &vtc->cursor);
+ }
+}
+
+// Append character to the console buffer.
+void vtconsole_append(vtconsole_t *vtc, char c)
+{
+ if (c == '\n')
+ {
+ vtconsole_newline(vtc);
+ }
+ else if (c == '\r')
+ {
+ vtc->cursor.x = 0;
+
+ if (vtc->on_move)
+ {
+ vtc->on_move(vtc, &vtc->cursor);
+ }
+ }
+ else if (c == '\t')
+ {
+ int n = 8 - (vtc->cursor.x % 8);
+ // storing all the tabs and their size encountered.
+ vtc->tabs[vtc->tab_index % LDISC_BUFFER_SIZE] = n;
+ vtc->tab_index++;
+
+ for (int i = 0; i < n; i++)
+ {
+ vtconsole_append(vtc, ' ');
+ }
+ }
+ else if (c == '\b')
+ {
+ if (vtc->cursor.x > 0)
+ {
+ vtc->cursor.x--;
+ }
+ else
+ {
+ vtc->cursor.y--;
+ vtc->cursor.x = vtc->width - 1;
+ }
+
+ if (vtc->on_move)
+ {
+ vtc->on_move(vtc, &vtc->cursor);
+ }
+
+ int i = (vtc->width * vtc->cursor.y) + vtc->cursor.x;
+ vtcell_t *cell = &vtc->buffer[i];
+ cell->attr = VTC_DEFAULT_ATTR;
+ cell->c = ' ';
+ vtc->on_paint(vtc, &vtc->buffer[i], i % vtc->width, i / vtc->width);
+ }
+ else
+ {
+ if (vtc->cursor.x >= vtc->width)
+ vtconsole_newline(vtc);
+
+ vtcell_t *cell =
+ &vtc->buffer[vtc->cursor.x + vtc->cursor.y * vtc->width];
+ cell->c = c;
+ cell->attr = vtc->attr;
+
+ if (vtc->on_paint)
+ {
+ vtc->on_paint(vtc, cell, vtc->cursor.x, vtc->cursor.y);
+ }
+
+ vtc->cursor.x++;
+
+ if (vtc->on_move)
+ {
+ vtc->on_move(vtc, &vtc->cursor);
+ }
+ }
+}
+
+// Helper function for vtconsole_process to move the cursor P1 rows up
+void vtconsole_csi_cuu(vtconsole_t *vtc, vtansi_arg_t *stack, int count)
+{
+ if (count == 1 && !stack[0].empty)
+ {
+ int attr = stack[0].value;
+ vtc->cursor.y = MAX(MIN(vtc->cursor.y - attr, vtc->height - 1), 1);
+ }
+
+ if (vtc->on_move)
+ {
+ vtc->on_move(vtc, &vtc->cursor);
+ }
+}
+
+// Helper function for vtconsole_process to move the cursor P1 columns left
+void vtconsole_csi_cud(vtconsole_t *vtc, vtansi_arg_t *stack, int count)
+{
+ if (count == 1 && !stack[0].empty)
+ {
+ int attr = stack[0].value;
+ vtc->cursor.y = MAX(MIN(vtc->cursor.y + attr, vtc->height - 1), 1);
+ }
+
+ if (vtc->on_move)
+ {
+ vtc->on_move(vtc, &vtc->cursor);
+ }
+}
+
+// Helper function for vtconsole_process to move the cursor P1 columns right
+void vtconsole_csi_cuf(vtconsole_t *vtc, vtansi_arg_t *stack, int count)
+{
+ if (count == 1 && !stack[0].empty)
+ {
+ int attr = stack[0].value;
+ vtc->cursor.x = MAX(MIN(vtc->cursor.x + attr, vtc->width - 1), 1);
+ }
+
+ if (vtc->on_move)
+ {
+ vtc->on_move(vtc, &vtc->cursor);
+ }
+}
+
+// Helper function for vtconsole_process to move the cursor P1 rows down
+void vtconsole_csi_cub(vtconsole_t *vtc, vtansi_arg_t *stack, int count)
+{
+ if (count == 1 && !stack[0].empty)
+ {
+ int attr = stack[0].value;
+ vtc->cursor.x = MAX(MIN(vtc->cursor.x - attr, vtc->width - 1), 1);
+ }
+
+ if (vtc->on_move)
+ {
+ vtc->on_move(vtc, &vtc->cursor);
+ }
+}
+
+// Helper function for vtconsole_process to place the cursor to the first
+// column of line P1 rows down from current
+void vtconsole_csi_cnl(vtconsole_t *vtc, vtansi_arg_t *stack, int count)
+{
+ if (count == 1 && !stack[0].empty)
+ {
+ int attr = stack[0].value;
+ vtc->cursor.y = MAX(MIN(vtc->cursor.y + attr, vtc->height - 1), 1);
+ vtc->cursor.x = 0;
+ }
+
+ if (vtc->on_move)
+ {
+ vtc->on_move(vtc, &vtc->cursor);
+ }
+}
+
+// Helper function for vtconsole_process to place the cursor to the first
+// column of line P1 rows up from current
+void vtconsole_csi_cpl(vtconsole_t *vtc, vtansi_arg_t *stack, int count)
+{
+ if (count == 1 && !stack[0].empty)
+ {
+ int attr = stack[0].value;
+ vtc->cursor.y = MAX(MIN(vtc->cursor.y - attr, vtc->height - 1), 1);
+ vtc->cursor.x = 0;
+ }
+
+ if (vtc->on_move)
+ {
+ vtc->on_move(vtc, &vtc->cursor);
+ }
+}
+
+// Helper function of vtconsole_process to move the cursor to column P1
+void vtconsole_csi_cha(vtconsole_t *vtc, vtansi_arg_t *stack, int count)
+{
+ if (count == 1 && !stack[0].empty)
+ {
+ int attr = stack[0].value;
+ vtc->cursor.y = MAX(MIN(attr, vtc->height - 1), 1);
+ }
+
+ if (vtc->on_move)
+ {
+ vtc->on_move(vtc, &vtc->cursor);
+ }
+}
+
+// Moves the cursor to row n, column m. The values are 1-based,
+void vtconsole_csi_cup(vtconsole_t *vtc, vtansi_arg_t *stack, int count)
+{
+ if (count == 1 && stack[0].empty)
+ {
+ vtc->cursor.x = 0;
+ vtc->cursor.y = 0;
+ }
+ else if (count == 2)
+ {
+ if (stack[0].empty)
+ {
+ vtc->cursor.y = 0;
+ }
+ else
+ {
+ vtc->cursor.y = MIN(stack[0].value - 1, vtc->height - 1);
+ }
+
+ if (stack[1].empty)
+ {
+ vtc->cursor.y = 0;
+ }
+ else
+ {
+ vtc->cursor.x = MIN(stack[1].value - 1, vtc->width - 1);
+ }
+ }
+
+ if (vtc->on_move)
+ {
+ vtc->on_move(vtc, &vtc->cursor);
+ }
+}
+
+// Clears part of the screen.
+void vtconsole_csi_ed(vtconsole_t *vtc, vtansi_arg_t *stack, int count)
+{
+ (void)(count);
+
+ vtcursor_t cursor = vtc->cursor;
+
+ if (stack[0].empty)
+ {
+ vtconsole_clear(vtc, cursor.x, cursor.y, vtc->width, vtc->height - 1);
+ }
+ else
+ {
+ int attr = stack[0].value;
+
+ if (attr == 0)
+ vtconsole_clear(vtc, cursor.x, cursor.y, vtc->width,
+ vtc->height - 1);
+ else if (attr == 1)
+ vtconsole_clear(vtc, 0, 0, cursor.x, cursor.y);
+ else if (attr == 2)
+ vtconsole_clear(vtc, 0, 0, vtc->width, vtc->height - 1);
+ }
+}
+
+// Erases part of the line.
+void vtconsole_csi_el(vtconsole_t *vtc, vtansi_arg_t *stack, int count)
+{
+ (void)(count);
+
+ vtcursor_t cursor = vtc->cursor;
+
+ if (stack[0].empty)
+ {
+ vtconsole_clear(vtc, cursor.x, cursor.y, vtc->width, cursor.y);
+ }
+ else
+ {
+ int attr = stack[0].value;
+
+ if (attr == 0)
+ vtconsole_clear(vtc, cursor.x, cursor.y, vtc->width, cursor.y);
+ else if (attr == 1)
+ vtconsole_clear(vtc, 0, cursor.y, cursor.x, cursor.y);
+ else if (attr == 2)
+ vtconsole_clear(vtc, 0, cursor.y, vtc->width, cursor.y);
+ }
+}
+
+// Sets the appearance of the following characters
+void vtconsole_csi_sgr(vtconsole_t *vtc, vtansi_arg_t *stack, int count)
+{
+ for (int i = 0; i < count; i++)
+ {
+ if (stack[i].empty || stack[i].value == 0)
+ {
+ vtc->attr = VTC_DEFAULT_ATTR;
+ }
+ else
+ {
+ int attr = stack[i].value;
+
+ if (attr == 1) // Increased intensity
+ {
+ vtc->attr.bright = 1;
+ }
+ else if (attr >= 30 && attr <= 37) // Set foreground color
+ {
+ vtc->attr.fg = attr - 30;
+ }
+ else if (attr >= 40 && attr <= 47) // Set background color
+ {
+ vtc->attr.bg = attr - 40;
+ }
+ }
+ }
+}
+
+void vtconsole_csi_l(vtconsole_t *vtc, vtansi_arg_t *stack, int count)
+{
+ if (count != 1)
+ {
+ return;
+ }
+ if (stack[0].empty || stack[0].value != 25)
+ {
+ return;
+ }
+
+ vga_disable_cursor();
+}
+
+void vtconsole_csi_h(vtconsole_t *vtc, vtansi_arg_t *stack, int count)
+{
+ if (count != 1)
+ {
+ return;
+ }
+
+ if (stack[0].empty || stack[0].value != 25)
+ {
+ return;
+ }
+
+ vga_enable_cursor();
+}
+
+// vtconsole_append is called by vtconsole_process to process and print the
+// keys pressed onto the console.
+void vtconsole_process(vtconsole_t *vtc, char c)
+{
+ vtansi_parser_t *parser = &vtc->ansiparser;
+
+ switch (parser->state)
+ {
+ case VTSTATE_ESC:
+ if (c == '\033')
+ {
+ parser->state = VTSTATE_BRACKET;
+
+ parser->index = 0;
+
+ parser->stack[parser->index].value = 0;
+ parser->stack[parser->index].empty = 1;
+ }
+ else
+ {
+ parser->state = VTSTATE_ESC;
+ vtconsole_append(vtc, c);
+ }
+ break;
+
+ case VTSTATE_BRACKET:
+ if (c == '[')
+ {
+ parser->state = VTSTATE_ATTR;
+ }
+ else
+ {
+ parser->state = VTSTATE_ESC;
+ vtconsole_append(vtc, c);
+ }
+ break;
+ case VTSTATE_ATTR:
+ if (c >= '0' && c <= '9')
+ {
+ parser->stack[parser->index].value *= 10;
+ parser->stack[parser->index].value += (c - '0');
+ parser->stack[parser->index].empty = 0;
+ }
+ else if (c == '?')
+ {
+ /* questionable (aka wrong) */
+ break;
+ }
+ else
+ {
+ if ((parser->index) < VTC_ANSI_PARSER_STACK_SIZE)
+ {
+ parser->index++;
+ }
+
+ parser->stack[parser->index].value = 0;
+ parser->stack[parser->index].empty = 1;
+
+ parser->state = VTSTATE_ENDVAL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (parser->state == VTSTATE_ENDVAL)
+ {
+ if (c == ';')
+ {
+ parser->state = VTSTATE_ATTR;
+ }
+ else
+ {
+ switch (c)
+ {
+ case 'A':
+ /* Cursor up P1 rows */
+ vtconsole_csi_cuu(vtc, parser->stack, parser->index);
+ break;
+ case 'B':
+ /* Cursor down P1 rows */
+ vtconsole_csi_cub(vtc, parser->stack, parser->index);
+ break;
+ case 'C':
+ /* Cursor right P1 columns */
+ vtconsole_csi_cuf(vtc, parser->stack, parser->index);
+ break;
+ case 'D':
+ /* Cursor left P1 columns */
+ vtconsole_csi_cud(vtc, parser->stack, parser->index);
+ break;
+ case 'E':
+ /* Cursor to first column of line P1 rows down from current
+ */
+ vtconsole_csi_cnl(vtc, parser->stack, parser->index);
+ break;
+ case 'F':
+ /* Cursor to first column of line P1 rows up from current */
+ vtconsole_csi_cpl(vtc, parser->stack, parser->index);
+ break;
+ case 'G':
+ /* Cursor to column P1 */
+ vtconsole_csi_cha(vtc, parser->stack, parser->index);
+ break;
+ case 'd':
+ /* Cursor left P1 columns */
+ break;
+ case 'H':
+ /* Moves the cursor to row n, column m. */
+ vtconsole_csi_cup(vtc, parser->stack, parser->index);
+ break;
+ case 'J':
+ /* Clears part of the screen. */
+ vtconsole_csi_ed(vtc, parser->stack, parser->index);
+ break;
+ case 'K':
+ /* Erases part of the line. */
+ vtconsole_csi_el(vtc, parser->stack, parser->index);
+ break;
+ case 'm':
+ /* Sets the appearance of the following characters */
+ vtconsole_csi_sgr(vtc, parser->stack, parser->index);
+ break;
+ case 'l':
+ vtconsole_csi_l(vtc, parser->stack, parser->index);
+ break;
+ case 'h':
+ vtconsole_csi_h(vtc, parser->stack, parser->index);
+ break;
+ }
+
+ parser->state = VTSTATE_ESC;
+ }
+ }
+}
+
+// vtconosle_putchar is called from vterminal_key_pressed
+void vtconsole_putchar(vtconsole_t *vtc, char c) { vtconsole_process(vtc, c); }
+
+// vtconsole_write is called from vterminal_write
+void vtconsole_write(vtconsole_t *vtc, const char *buffer, uint32_t size)
+{
+ // looping through the whole size of the buffer
+ for (uint32_t i = 0; i < size; i++)
+ {
+ // acquiting the ldisc associated with the vtconsole/vterminal
+ ldisc_t *new_ldisc = &vterminal_to_tty(vtc)->tty_ldisc;
+
+ // checking if the buffer is a backspsace and the last entered character was a tab
+ if (buffer[i] == '\b' && new_ldisc->ldisc_buffer[(new_ldisc->ldisc_head)] == '\t')
+ {
+ // calling vtcomsole_process 'n' number of times.
+ // where 'n' is the size of the tab.
+ for (int j = 0; j < vtc->tabs[(vtc->tab_index - 1) % LDISC_BUFFER_SIZE]; j++)
+ {
+ vtconsole_process(vtc, buffer[i]);
+ }
+ vtc->tab_index--;
+ }
+ else
+ {
+ vtconsole_process(vtc, buffer[i]);
+ }
+ }
+}
+
+// called by vterminal_make_active to redraw the console.
+void vtconsole_redraw(vtconsole_t *vtc)
+{
+ for (int i = 0; i < (vtc->width * vtc->height); i++)
+ {
+ if (vtc->on_paint)
+ {
+ vtc->on_paint(vtc, &vtc->buffer[i], i % vtc->width, i / vtc->width);
+ }
+ }
+}
+
+#define VGA_COLOR(__fg, __bg) (__bg << 4 | __fg)
+#define VGA_ENTRY(__c, __fg, __bg) \
+ ((((__bg)&0XF) << 4 | ((__fg)&0XF)) << 8 | ((__c)&0XFF))
+
+// helper function for paint_callback.
+void vga_cell(unsigned int x, unsigned int y, unsigned short entry)
+{
+ if (x < VGA_SCREEN_WIDTH)
+ {
+ if (y < VGA_SCREEN_WIDTH)
+ {
+ vga_write_char_at(y, x, entry);
+ }
+ }
+}
+
+static char colors[] = {
+ [VTCOLOR_BLACK] = VGACOLOR_BLACK,
+ [VTCOLOR_RED] = VGACOLOR_RED,
+ [VTCOLOR_GREEN] = VGACOLOR_GREEN,
+ [VTCOLOR_YELLOW] = VGACOLOR_BROWN,
+ [VTCOLOR_BLUE] = VGACOLOR_BLUE,
+ [VTCOLOR_MAGENTA] = VGACOLOR_MAGENTA,
+ [VTCOLOR_CYAN] = VGACOLOR_CYAN,
+ [VTCOLOR_GREY] = VGACOLOR_LIGHT_GRAY,
+};
+
+static char brightcolors[] = {
+ [VTCOLOR_BLACK] = VGACOLOR_GRAY,
+ [VTCOLOR_RED] = VGACOLOR_LIGHT_RED,
+ [VTCOLOR_GREEN] = VGACOLOR_LIGHT_GREEN,
+ [VTCOLOR_YELLOW] = VGACOLOR_LIGHT_YELLOW,
+ [VTCOLOR_BLUE] = VGACOLOR_LIGHT_BLUE,
+ [VTCOLOR_MAGENTA] = VGACOLOR_LIGHT_MAGENTA,
+ [VTCOLOR_CYAN] = VGACOLOR_LIGHT_CYAN,
+ [VTCOLOR_GREY] = VGACOLOR_WHITE,
+};
+
+static vterminal_t *active_vt = NULL;
+
+// used for initializing the vtconsoles.
+void paint_callback(vtconsole_t *vtc, vtcell_t *cell, int x, int y)
+{
+ if (vtc != active_vt)
+ {
+ return;
+ }
+
+ if (cell->attr.bright)
+ {
+ vga_cell(x, y,
+ VGA_ENTRY(cell->c, brightcolors[cell->attr.fg],
+ colors[cell->attr.bg]));
+ }
+ else
+ {
+ vga_cell(
+ x, y,
+ VGA_ENTRY(cell->c, colors[cell->attr.fg], colors[cell->attr.bg]));
+ }
+}
+
+// used for initializing the vtconsoles.
+void cursor_move_callback(vtconsole_t *vtc, vtcursor_t *cur)
+{
+ if (vtc != active_vt)
+ {
+ return;
+ }
+ vga_set_cursor(cur->y, cur->x);
+}
+
+// initialization function for vterminal which calls the vtconsole constructor
+void vterminal_init(vtconsole_t *vt)
+{
+ vtconsole(vt, VGA_SCREEN_WIDTH, VGA_SCREEN_HEIGHT, paint_callback,
+ cursor_move_callback);
+}
+
+// Used in tty.c to make a vterminal active and working.
+void vterminal_make_active(vterminal_t *vt)
+{
+ active_vt = vt;
+ vtconsole_redraw(vt);
+ vga_set_cursor(vt->cursor.y, vt->cursor.x);
+}
+
+// called by ldisc_key_pressed from ldisc.c
+void vterminal_key_pressed(vterminal_t *vt)
+{
+ char buf[LDISC_BUFFER_SIZE];
+ size_t len =
+ ldisc_get_current_line_raw(&vterminal_to_tty(vt)->tty_ldisc, buf);
+ vtconsole_putchar(vt, buf[len - 1]);
+}
+
+void vterminal_scroll_to_bottom(vterminal_t *vt) { KASSERT(0); }
+
+// ldisc_key_pressed calls this vterminal_write if VGA_BUF is not specified.
+size_t vterminal_write(vterminal_t *vt, const char *buf, size_t len)
+{
+ vtconsole_write(vt, buf, len);
+ return len;
+}
+
+// could be used in ldisc_key_pressed
+size_t vterminal_echo_input(vterminal_t *vt, const char *buf, size_t len)
+{
+ vtconsole_write(vt, buf, len);
+ return len;
+}