diff options
author | nthnluu <nate1299@me.com> | 2024-01-28 21:20:27 -0500 |
---|---|---|
committer | nthnluu <nate1299@me.com> | 2024-01-28 21:20:27 -0500 |
commit | c63f340d90800895f007de64b7d2d14624263331 (patch) | |
tree | 2c0849fa597dd6da831c8707b6f2603403778d7b /kernel/drivers |
Created student weenix repository
Diffstat (limited to 'kernel/drivers')
-rw-r--r-- | kernel/drivers/Submodules | 1 | ||||
-rw-r--r-- | kernel/drivers/blockdev.c | 96 | ||||
-rw-r--r-- | kernel/drivers/chardev.c | 43 | ||||
-rw-r--r-- | kernel/drivers/cmos.c | 78 | ||||
-rw-r--r-- | kernel/drivers/disk/sata.c | 512 | ||||
-rw-r--r-- | kernel/drivers/keyboard.c | 208 | ||||
-rw-r--r-- | kernel/drivers/memdevs.c | 108 | ||||
-rw-r--r-- | kernel/drivers/pcie.c | 77 | ||||
-rw-r--r-- | kernel/drivers/screen.c | 513 | ||||
-rw-r--r-- | kernel/drivers/tty/ldisc.c | 120 | ||||
-rw-r--r-- | kernel/drivers/tty/tty.c | 135 | ||||
-rw-r--r-- | kernel/drivers/tty/vterminal.c | 1384 |
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; +} |