207 lines
5.4 KiB
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);
|
|
|
|
}
|
|
|