TSOS/src/kernel/drivers/vga/screen.c

207 lines
5.4 KiB
C

/*
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 <stdarg.h>
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);
}