Files

613 lines
24 KiB
C

/* lib.c - Some basic library functions (printf, strlen, etc.)
* vim:ts=4 noexpandtab */
#include "lib.h"
#include "../interrupts/multiprocessing.h"
#include "../devices/vga_text.h"
#include "../devices/qemu_vga.h"
char* video_mem = (char *)VIDEO;
uint8_t is_clied = 0;
/* void infinite_loop()
* @description: an environment-friendly infinite loop.
* puts the processor into infinite sleep.
*/
void infinite_loop() {
asm volatile (".1: hlt; jmp .1;");
}
/* void wait_interrupt()
* @description: halt the CPU until next interrupt occurs.
* used to save some power.
*/
void wait_interrupt() {
asm volatile("hlt");
}
/* void clear(void);
* Inputs: void
* Return Value: none
* Function: Clears video memory */
void clear(void) {
int32_t i;
for (i = 0; i < NUM_ROWS * NUM_COLS; i++) {
*(uint8_t *)(video_mem + (i << 1)) = ' ';
*(uint8_t *)(video_mem + (i << 1) + 1) = ATTRIB;
}
qemu_vga_clear();
terminals[active_terminal_id].screen_x = 0;
terminals[active_terminal_id].screen_y = 0;
vga_text_set_cursor_pos(0, 0);
qemu_vga_set_cursor_pos(0, 0);
}
/* void clear_row(uint32_t row)
* @input: row - the id of row to be cleared, should be in range 0 to (NUM_ROWS - 1)
* @output: the row on screen gets cleared
* @description: clear one row on the screen
*/
void clear_row(uint32_t row) {
int32_t i;
for(i = row * NUM_COLS; i < (row + 1) * NUM_COLS; i++) {
*(uint8_t *)(video_mem + (i << 1)) = ' ';
*(uint8_t *)(video_mem + (i << 1) + 1) = ATTRIB;
}
qemu_vga_clear_row(row);
}
/* Standard printf().
* Only supports the following format strings:
* %% - print a literal '%' character
* %x - print a number in hexadecimal
* %u - print a number as an unsigned integer
* %d - print a number as a signed integer
* %c - print a character
* %s - print a string
* %#x - print a number in 32-bit aligned hexadecimal, i.e.
* print 8 hexadecimal digits, zero-padded on the left.
* For example, the hex number "E" would be printed as
* "0000000E".
* Note: This is slightly different than the libc specification
* for the "#" modifier (this implementation doesn't add a "0x" at
* the beginning), but I think it's more flexible this way.
* Also note: %x is the only conversion specifier that can use
* the "#" modifier to alter output. */
int32_t printf(int8_t *format, ...) {
/* Pointer to the format string */
int8_t* buf = format;
/* Stack pointer for the other parameters */
int32_t* esp = (void *)&format;
esp++;
while (*buf != '\0') {
switch (*buf) {
case '%':
{
int32_t alternate = 0;
buf++;
format_char_switch:
/* Conversion specifiers */
switch (*buf) {
/* Print a literal '%' character */
case '%':
putc('%');
break;
/* Use alternate formatting */
case '#':
alternate = 1;
buf++;
/* Yes, I know gotos are bad. This is the
* most elegant and general way to do this,
* IMHO. */
goto format_char_switch;
/* Print a number in hexadecimal form */
case 'x':
{
int8_t conv_buf[64];
if (alternate == 0) {
itoa(*((uint32_t *)esp), conv_buf, 16);
puts(conv_buf);
} else {
int32_t starting_index;
int32_t i;
itoa(*((uint32_t *)esp), &conv_buf[8], 16);
i = starting_index = strlen(&conv_buf[8]);
while(i < 8) {
conv_buf[i] = '0';
i++;
}
puts(&conv_buf[starting_index]);
}
esp++;
}
break;
/* Print a number in unsigned int form */
case 'u':
{
int8_t conv_buf[36];
itoa(*((uint32_t *)esp), conv_buf, 10);
puts(conv_buf);
esp++;
}
break;
/* Print a number in signed int form */
case 'd':
{
int8_t conv_buf[36];
int32_t value = *((int32_t *)esp);
if(value < 0) {
conv_buf[0] = '-';
itoa(-value, &conv_buf[1], 10);
} else {
itoa(value, conv_buf, 10);
}
puts(conv_buf);
esp++;
}
break;
/* Print a single character */
case 'c':
putc((uint8_t) *((int32_t *)esp));
esp++;
break;
/* Print a NULL-terminated string */
case 's':
puts(*((int8_t **)esp));
esp++;
break;
default:
break;
}
}
break;
default:
putc(*buf);
break;
}
buf++;
}
return (buf - format);
}
/* int32_t puts(int8_t* s);
* Inputs: int_8* s = pointer to a string of characters
* Return Value: Number of bytes written
* Function: Output a string to the console */
int32_t puts(int8_t* s) {
register int32_t index = 0;
while (s[index] != '\0') {
putc(s[index]);
index++;
}
return index;
}
/* void putc(uint8_t c);
* Inputs: uint_8* c = character to print
* Return Value: void
* Function: Output a character to the console */
void putc(uint8_t c)
{
// if reach the right bottom of the screen
if (NUM_COLS * terminals[active_terminal_id].screen_y + terminals[active_terminal_id].screen_x
>= NUM_COLS * NUM_ROWS) roll_up();
if(!(UTF8_MASK & c)) {
// This char isn't related to UTF8,
// the previous UTF-8 code is broken, ignore it
terminals[active_terminal_id].utf8_state.got = 0;
} else if(UTF8_3BYTE_MASK == (c & UTF8_3BYTE_MASK)) {
// This is the beginning of a 3 byte UTF-8 code
terminals[active_terminal_id].utf8_state.got = 3;
} else if(UTF8_2BYTE_MASK == (c & UTF8_2BYTE_MASK)) {
// This is the beginning of a 2 byte UTF-8 code
terminals[active_terminal_id].utf8_state.got = 2;
} else if(terminals[active_terminal_id].utf8_state.got > 0) {
// This is the 2nd or 3rd byte of a UTF-8 code
// Decrease counter of expected length by 1
terminals[active_terminal_id].utf8_state.got--;
}
// If input is a line feed
if(c == '\n' || c == '\r') {
terminals[active_terminal_id].screen_y++;
if (terminals[active_terminal_id].screen_y >= NUM_ROWS) roll_up();
clear_row(terminals[active_terminal_id].screen_y); // Clear the new line for better display
terminals[active_terminal_id].screen_x = 0;
} else if(c == BACKSPACE) {
if(terminals[active_terminal_id].keyboard_buffer_enable) {
// Do not let user delete more than typed
if(terminals[active_terminal_id].keyboard_buffer_top <= 0) return;
} else {
// Do not let user delete to previous line
if(terminals[active_terminal_id].screen_x <= 0) return;
}
// Move back one character
terminals[active_terminal_id].screen_x--;
// If the line is filled up
if(terminals[active_terminal_id].screen_x < 0) {
// clear_row(terminals[active_terminal_id].screen_y);
terminals[active_terminal_id].screen_x = NUM_COLS - 1;
terminals[active_terminal_id].screen_y -= 1;
if(terminals[active_terminal_id].screen_y < 0) {
terminals[active_terminal_id].screen_x = 0;
terminals[active_terminal_id].screen_y = 0;
}
}
// Clear the current character
*(uint8_t *)(video_mem + ((NUM_COLS * terminals[active_terminal_id].screen_y + terminals[active_terminal_id].screen_x) << 1)) = ' ';
*(uint8_t *)(video_mem + ((NUM_COLS * terminals[active_terminal_id].screen_y + terminals[active_terminal_id].screen_x) << 1) + 1) = ATTRIB;
qemu_vga_putc(terminals[active_terminal_id].screen_x * FONT_ACTUAL_WIDTH,
terminals[active_terminal_id].screen_y * FONT_ACTUAL_HEIGHT,
' ', qemu_vga_get_terminal_color(ATTRIB), qemu_vga_get_terminal_color(ATTRIB >> 4));
} else {
if(terminals[active_terminal_id].utf8_state.got == 0) {
// The input char has no relation to UTF-8, simply print it
*(uint8_t *)(video_mem + ((NUM_COLS * terminals[active_terminal_id].screen_y + terminals[active_terminal_id].screen_x) << 1)) = c;
*(uint8_t *)(video_mem + ((NUM_COLS * terminals[active_terminal_id].screen_y + terminals[active_terminal_id].screen_x) << 1) + 1) = ATTRIB;
} else if(terminals[active_terminal_id].utf8_state.got < 3) {
// The input char is the second last char of UTF-8 code
// As VGA text mode cannot display these characters,
// Create a space for it, as each Chinese character is 2 letters wide.
// With one space at got == 2 and one at got == 1, 2 spaces are made.
*(uint8_t *)(video_mem + ((NUM_COLS * terminals[active_terminal_id].screen_y + terminals[active_terminal_id].screen_x) << 1)) = ' ';
*(uint8_t *)(video_mem + ((NUM_COLS * terminals[active_terminal_id].screen_y + terminals[active_terminal_id].screen_x) << 1) + 1) = ATTRIB;
}
if(terminals[active_terminal_id].utf8_state.got == 0) {
// Tell QEMU VGA to put a character at the same position
qemu_vga_putc(terminals[active_terminal_id].screen_x * FONT_ACTUAL_WIDTH,
terminals[active_terminal_id].screen_y * FONT_ACTUAL_HEIGHT,
c, qemu_vga_get_terminal_color(ATTRIB), qemu_vga_get_terminal_color(ATTRIB >> 4));
terminals[active_terminal_id].screen_x++;
} else if(terminals[active_terminal_id].utf8_state.got == 1) {
// Last call to draw the character.
// Tell QEMU VGA to put a character at one letter before,
// in the 2 space for a Chinese character
if(terminals[active_terminal_id].screen_x > 0) terminals[active_terminal_id].screen_x--;
qemu_vga_putc(terminals[active_terminal_id].screen_x * FONT_ACTUAL_WIDTH,
terminals[active_terminal_id].screen_y * FONT_ACTUAL_HEIGHT,
c, qemu_vga_get_terminal_color(ATTRIB), qemu_vga_get_terminal_color(ATTRIB >> 4));
// And then create space for it
terminals[active_terminal_id].screen_x += 2;
} else if(terminals[active_terminal_id].utf8_state.got == 2) {
// Second last call to draw the character.
// Create the first of the two space for the Chinese Character,
// and inform QEMU VGA (it won't draw anything yet)
qemu_vga_putc(terminals[active_terminal_id].screen_x * FONT_ACTUAL_WIDTH,
terminals[active_terminal_id].screen_y * FONT_ACTUAL_HEIGHT,
c, qemu_vga_get_terminal_color(ATTRIB), qemu_vga_get_terminal_color(ATTRIB >> 4));
terminals[active_terminal_id].screen_x++;
} else {
// First of the three calls to draw this character, simply inform QEMU VGA
qemu_vga_putc(terminals[active_terminal_id].screen_x * FONT_ACTUAL_WIDTH,
terminals[active_terminal_id].screen_y * FONT_ACTUAL_HEIGHT,
c, qemu_vga_get_terminal_color(ATTRIB), qemu_vga_get_terminal_color(ATTRIB >> 4));
}
// Handle finishing of one line and moving onto next line
if(terminals[active_terminal_id].screen_x >= NUM_COLS) { // If the line is filled up
terminals[active_terminal_id].screen_x = 0;
terminals[active_terminal_id].screen_y++;
// if reach the right bottom of the screen
if (terminals[active_terminal_id].screen_y >= NUM_ROWS) roll_up();
clear_row(terminals[active_terminal_id].screen_y); // Clear the new line for better display
}
}
// Synchronize cursor position with VGA
vga_text_set_cursor_pos(terminals[active_terminal_id].screen_x, terminals[active_terminal_id].screen_y);
qemu_vga_set_cursor_pos(terminals[active_terminal_id].screen_x, terminals[active_terminal_id].screen_y);
}
/* void roll_up();
* Inputs: none
* Return Value: void
* Function:roll the page up one line */
void roll_up() {
memcpy(video_mem, video_mem + (NUM_COLS << 1), (NUM_ROWS - 1) * NUM_COLS * 2);
terminals[active_terminal_id].screen_x = 0;
terminals[active_terminal_id].screen_y = NUM_ROWS - 1;
qemu_vga_roll_up();
qemu_vga_set_cursor_pos(0, NUM_ROWS - 1);
clear_row(NUM_ROWS - 1);
}
/* int8_t* itoa(uint32_t value, int8_t* buf, int32_t radix);
* Inputs: uint32_t value = number to convert
* int8_t* buf = allocated buffer to place string in
* int32_t radix = base system. hex, oct, dec, etc.
* Return Value: number of bytes written
* Function: Convert a number to its ASCII representation, with base "radix" */
int8_t* itoa(uint32_t value, int8_t* buf, int32_t radix) {
static int8_t lookup[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
int8_t *newbuf = buf;
int32_t i;
uint32_t newval = value;
/* Special case for zero */
if (value == 0) {
buf[0] = '0';
buf[1] = '\0';
return buf;
}
/* Go through the number one place value at a time, and add the
* correct digit to "newbuf". We actually add characters to the
* ASCII string from lowest place value to highest, which is the
* opposite of how the number should be printed. We'll reverse the
* characters later. */
while (newval > 0) {
i = newval % radix;
*newbuf = lookup[i];
newbuf++;
newval /= radix;
}
/* Add a terminating NULL */
*newbuf = '\0';
/* Reverse the string and return */
return strrev(buf);
}
/* int8_t* strrev(int8_t* s);
* Inputs: int8_t* s = string to reverse
* Return Value: reversed string
* Function: reverses a string s */
int8_t* strrev(int8_t* s) {
register int8_t tmp;
register int32_t beg = 0;
register int32_t end = strlen(s) - 1;
while (beg < end) {
tmp = s[end];
s[end] = s[beg];
s[beg] = tmp;
beg++;
end--;
}
return s;
}
/* uint32_t strlen(const int8_t* s);
* Inputs: const int8_t* s = string to take length of
* Return Value: length of string s
* Function: return length of string s */
uint32_t strlen(const int8_t* s) {
register uint32_t len = 0;
while (s[len] != '\0')
len++;
return len;
}
/* void* memset(void* s, int32_t c, uint32_t n);
* Inputs: void* s = pointer to memory
* int32_t c = value to set memory to
* uint32_t n = number of bytes to set
* Return Value: new string
* Function: set n consecutive bytes of pointer s to value c */
void* memset(void* s, int32_t c, uint32_t n) {
c &= 0xFF;
asm volatile (" \n\
.memset_top: \n\
testl %%ecx, %%ecx \n\
jz .memset_done \n\
testl $0x3, %%edi \n\
jz .memset_aligned \n\
movb %%al, (%%edi) \n\
addl $1, %%edi \n\
subl $1, %%ecx \n\
jmp .memset_top \n\
.memset_aligned: \n\
movw %%ds, %%dx \n\
movw %%dx, %%es \n\
movl %%ecx, %%edx \n\
shrl $2, %%ecx \n\
andl $0x3, %%edx \n\
cld \n\
rep stosl \n\
.memset_bottom: \n\
testl %%edx, %%edx \n\
jz .memset_done \n\
movb %%al, (%%edi) \n\
addl $1, %%edi \n\
subl $1, %%edx \n\
jmp .memset_bottom \n\
.memset_done: \n\
"
:
: "a"(c << 24 | c << 16 | c << 8 | c), "D"(s), "c"(n)
: "edx", "memory", "cc"
);
return s;
}
/* void* memset_word(void* s, int32_t c, uint32_t n);
* Description: Optimized memset_word
* Inputs: void* s = pointer to memory
* int32_t c = value to set memory to
* uint32_t n = number of bytes to set
* Return Value: new string
* Function: set lower 16 bits of n consecutive memory locations of pointer s to value c */
void* memset_word(void* s, int32_t c, uint32_t n) {
asm volatile (" \n\
movw %%ds, %%dx \n\
movw %%dx, %%es \n\
cld \n\
rep stosw \n\
"
:
: "a"(c), "D"(s), "c"(n)
: "edx", "memory", "cc"
);
return s;
}
/* void* memset_dword(void* s, int32_t c, uint32_t n);
* Inputs: void* s = pointer to memory
* int32_t c = value to set memory to
* uint32_t n = number of bytes to set
* Return Value: new string
* Function: set n consecutive memory locations of pointer s to value c */
void* memset_dword(void* s, int32_t c, uint32_t n) {
asm volatile (" \n\
movw %%ds, %%dx \n\
movw %%dx, %%es \n\
cld \n\
rep stosl \n\
"
:
: "a"(c), "D"(s), "c"(n)
: "edx", "memory", "cc"
);
return s;
}
/* void* memcpy(void* dest, const void* src, uint32_t n);
* Inputs: void* dest = destination of copy
* const void* src = source of copy
* uint32_t n = number of byets to copy
* Return Value: pointer to dest
* Function: copy n bytes of src to dest */
void* memcpy(void* dest, const void* src, uint32_t n) {
asm volatile (" \n\
.memcpy_top: \n\
testl %%ecx, %%ecx \n\
jz .memcpy_done \n\
testl $0x3, %%edi \n\
jz .memcpy_aligned \n\
movb (%%esi), %%al \n\
movb %%al, (%%edi) \n\
addl $1, %%edi \n\
addl $1, %%esi \n\
subl $1, %%ecx \n\
jmp .memcpy_top \n\
.memcpy_aligned: \n\
movw %%ds, %%dx \n\
movw %%dx, %%es \n\
movl %%ecx, %%edx \n\
shrl $2, %%ecx \n\
andl $0x3, %%edx \n\
cld \n\
rep movsl \n\
.memcpy_bottom: \n\
testl %%edx, %%edx \n\
jz .memcpy_done \n\
movb (%%esi), %%al \n\
movb %%al, (%%edi) \n\
addl $1, %%edi \n\
addl $1, %%esi \n\
subl $1, %%edx \n\
jmp .memcpy_bottom \n\
.memcpy_done: \n\
"
:
: "S"(src), "D"(dest), "c"(n)
: "eax", "edx", "memory", "cc"
);
return dest;
}
/* void* memmove(void* dest, const void* src, uint32_t n);
* Description: Optimized memmove (used for overlapping memory areas)
* Inputs: void* dest = destination of move
* const void* src = source of move
* uint32_t n = number of byets to move
* Return Value: pointer to dest
* Function: move n bytes of src to dest */
void* memmove(void* dest, const void* src, uint32_t n) {
asm volatile (" \n\
movw %%ds, %%dx \n\
movw %%dx, %%es \n\
cld \n\
cmp %%edi, %%esi \n\
jae .memmove_go \n\
leal -1(%%esi, %%ecx), %%esi \n\
leal -1(%%edi, %%ecx), %%edi \n\
std \n\
.memmove_go: \n\
rep movsb \n\
"
:
: "D"(dest), "S"(src), "c"(n)
: "edx", "memory", "cc"
);
return dest;
}
/* int32_t strncmp(const int8_t* s1, const int8_t* s2, uint32_t n)
* Inputs: const int8_t* s1 = first string to compare
* const int8_t* s2 = second string to compare
* uint32_t n = number of bytes to compare
* Return Value: A zero value indicates that the characters compared
* in both strings form the same string.
* A value greater than zero indicates that the first
* character that does not match has a greater value
* in str1 than in str2; And a value less than zero
* indicates the opposite.
* Function: compares string 1 and string 2 for equality */
int32_t strncmp(const int8_t* s1, const int8_t* s2, uint32_t n) {
int32_t i;
for (i = 0; i < n; i++) {
if ((s1[i] != s2[i]) || (s1[i] == '\0') /* || s2[i] == '\0' */) {
/* The s2[i] == '\0' is unnecessary because of the short-circuit
* semantics of 'if' expressions in C. If the first expression
* (s1[i] != s2[i]) evaluates to false, that is, if s1[i] ==
* s2[i], then we only need to test either s1[i] or s2[i] for
* '\0', since we know they are equal. */
return s1[i] - s2[i];
}
}
return 0;
}
/* int8_t* strcpy(int8_t* dest, const int8_t* src)
* Inputs: int8_t* dest = destination string of copy
* const int8_t* src = source string of copy
* Return Value: pointer to dest
* Function: copy the source string into the destination string */
int8_t* strcpy(int8_t* dest, const int8_t* src) {
int32_t i = 0;
while (src[i] != '\0') {
dest[i] = src[i];
i++;
}
dest[i] = '\0';
return dest;
}
/* int8_t* strcpy(int8_t* dest, const int8_t* src, uint32_t n)
* Inputs: int8_t* dest = destination string of copy
* const int8_t* src = source string of copy
* uint32_t n = number of bytes to copy
* Return Value: pointer to dest
* Function: copy n bytes of the source string into the destination string */
int8_t* strncpy(int8_t* dest, const int8_t* src, uint32_t n) {
int32_t i = 0;
while (src[i] != '\0' && i < n) {
dest[i] = src[i];
i++;
}
while (i < n) {
dest[i] = '\0';
i++;
}
return dest;
}