/* Copyright 2022 Mattia Giambirtone & Contributors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Implementation of a simple text-only VGA driver #include "kernel/drivers/vga/screen.h" #include "kernel/types.h" #include "kernel/util.h" #include u32 getCursorOffset(void); void setCursorOffset(u32 offset); void putchar(char ch, char attr); // Public API below void kprint(const char* message) { /* Prints a null-terminated string to the VGA text buffer */ while (*message) { putchar(*(message++), 0); } } // TODO void kprintf(const char *fmt, ...) { /* A simple reimplementation of printf in kernel space. The following basic format specifiers can be used: - %s -> string - %d -> signed number - %u -> unsigned number - %c -> character */ va_list args; va_start(args, fmt); char* sbuf; /* Used to print strings */ char ibuf[12]; /* Used as input to itoa. 12 is the number of digits of 2 ** 32 + 2 (for the sign and null byte)*/ memset(ibuf, '0', 12); while (*fmt) { putchar(*(fmt++), 0); } va_end(args); } void inline kprintln(const char* message) { /* Identical to kprint, but calls kprint("\n") afterwards */ kprint(message); kprint("\n"); } // Private API below void putchar(char ch, char attr) { /* Writes a single character to the VGA text buffer If the attr byte is zero, its value is the result of the expansion of the LIGHT_GREY_ON_BLACK macro. The following escape sequences are handled: - '\n' -> Goes to the next line - '\t' -> Prints VGA_TABSIZE spaces - '\r' -> Goes to the beginning of the line */ if (!attr) { attr = LIGHT_GREY_ON_BLACK; } u32 offset = getCursorOffset(); switch (ch) { case '\r': offset -= offset % MAX_COLS; break; case '\n': offset += MAX_COLS - offset % MAX_COLS; break; case '\t': for (i32 i = 0; i < VGA_TABSIZE; i++) { VMEM_BUF[offset] = ' '; VMEM_BUF[offset + 1] = attr; offset += 2; } break; default: VMEM_BUF[offset] = ch; VMEM_BUF[offset + 1] = attr; offset += 2; break; } /* // We check if we reached the end of the screen, in which // case we scroll if (offset >= SCREEN_SIZE * 2) { for (u8 i = 1; i < MAX_ROWS; i++) { // This loop will take the bytes of row i and copy // them to row i - 1, effectively erasing the first // one and causing the text on the screen to scroll! copystr((char*)(getOffset(i, 0) + VMEM_ADDRESS), (char*)(getOffset(i - 1, 0) + VMEM_ADDRESS), MAX_COLS * 2); } // We empty the last line char* last = (char*)(getOffset(MAX_ROWS - 1, 0) + VMEM_ADDRESS); for (u8 i = 0; i < MAX_COLS * 2; i++) { last[i] = 0; } offset -= 2 * MAX_COLS; } */ setCursorOffset(offset); return offset; } u32 getCursorOffset(void) { /* Returns the current offset of the VGA text cursor */ // We request the high byte of the // cursor position by writing to the // VGA control port. We can then read // the result on the VGA data port. // Memory-mapped I/O is great, isn't it? writeByte(REG_SCREEN_CTRL, 14); // And we read it u32 offset = readByte(REG_SCREEN_DATA); offset <<= 8; // Now we request the low byte of the cursor // position writeByte(REG_SCREEN_CTRL, 15); offset += readByte(REG_SCREEN_DATA); return offset * 2; // Times 2 because VGA cells are 2 bytes long } void setCursorOffset(u32 offset) { /* Sets the offset of the VGA text cursor to the desired value. Note that, this being a low level wrapper around I/O ports, no bounds checking is performed: the caller should make sure that the offset fits within the 80x25 grid of the VGA screen */ // We now divide by 2 because while we count // in bytes, the VGA controller counts in cells offset /= 2; // This function is almost identical to getCursorOffset, // except we're now writing on the bus instead of reading // from it, so the VGA controller updates the cursor position // We write the high bits first writeByte(REG_SCREEN_CTRL, 14); writeByte(REG_SCREEN_DATA, HIGH8(offset)); // Then the low bits writeByte(REG_SCREEN_CTRL, 15); writeByte(REG_SCREEN_DATA, LOW8(offset)); } void clearScreen(void) { /* Clears the screen and resets the cursor position to 0 */ for (i32 i = 0; i < SCREEN_SIZE; i++) { VMEM_BUF[i * 2] = ' '; // The screen is actually never "empty", just filled with spaces VMEM_BUF[i * 2 + 1] = LIGHT_GREY_ON_BLACK; } setCursorOffset(0); }