diff --git a/Makefile b/Makefile index 725ba65..96c8964 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ VPATH := $(SRC_DIRS) all: clean kernel.bin qemu # Assembly start.o goes to build/ -$(OUT_DIR)start.o: kernel/start.S +$(OUT_DIR)start.o: kernel/start.s @mkdir -p $(OUT_DIR) $(AS) -c $< -o $@ @@ -53,4 +53,4 @@ qemu: @echo "Press Ctrl-A then X to exit QEMU" @qemu-system-arm -M versatilepb -nographic -kernel $(OUT_DIR)kernel.bin -.PHONY: all clean qemu \ No newline at end of file +.PHONY: all clean qemu diff --git a/doc/.gitignore b/doc/.gitignore index 908282c..1cd6ca6 100644 --- a/doc/.gitignore +++ b/doc/.gitignore @@ -1,4 +1,4 @@ -# esclude the /bin directory +# exclude the /bin directory # This is the directory where the compiled files are stored # It is not necessary to include it in the repository -/bin \ No newline at end of file +/bin diff --git a/doc/contents/puts.tex b/doc/contents/puts.tex new file mode 100644 index 0000000..86d5512 --- /dev/null +++ b/doc/contents/puts.tex @@ -0,0 +1,60 @@ +\documentclass{article} +\usepackage{listings} +\usepackage{geometry} +\geometry{margin=1in} +\title{I/O API} +\date{} +\begin{document} + +\maketitle + +\section*{puts(char *s, ...)} +\subsection*{Description} +Sends a null-terminated format string over UART. If an incorrect datatype is given for a format specifier, \underline{the behavior is undefined}. If a format specifier is given without a matching argument, it is simply skipped when the string is outputted. The following format specifiers are supported: + +\begin{itemize} + \item \textbf{\%c}: Expects a single character. + \item \textbf{\%s}: Expects a null-terminated string. + \item \textbf{\%d}: For 32-bit signed integers in the range \texttt{-2147483648} to \texttt{2147483647}. + \item \textbf{\%ld}: For 64-bit signed integers in the range \texttt{-9223372036854775808} to \texttt{9223372036854775807}, excluding the range specified above, for \textbf{\%d}. + \item \textbf{\%lu}: For 64-bit unsigned integers in the range \texttt{2147483648} to \texttt{18446744073709551615}. + \item \textbf{\%x} and \textbf{\%X}: For 32-bit unsigned integers in the range \texttt{0} to \texttt{2147483647}, where the case of \texttt{x} determines the case of the hexadecimal digits (\texttt{a-f} or \texttt{A-F}). + \item \textbf{\%lx} and \textbf{\%lX}: For 64-bit unsigned integers printed in hexadecimal format. Has the same range as \textbf{\%lu}. The case of \texttt{x} determines the case of the hexadecimal digits (\texttt{a-f} or \texttt{A-F}). + \item \textbf{\%\%}: Outputs a '\%'. +\end{itemize} + +\begin{flushleft} +All the ranges specified above are inclusive. Also, note that integers in the range \texttt{-2147483648} to \texttt{2147483647} are passed as 32 bit integers and any integers not part of this range are passed as 64 bit integers \underline{by default}. +If desired, integers of this range can be cast as \texttt{long long} or \texttt{unsigned long long} +\newline +for use with the format specifiers prefixed by \textbf{l}(ell). +\end{flushleft} + +\subsection*{Examples} +\begin{lstlisting}[language=C,showstringspaces=false] +// Printing long long integers +puts("%lu %ld %ld\n", 18446744073709551615, -9223372036854775808, +9223372036854775807); + +// Printing 32-bit signed integers +puts("%d %d\n", 2147483647, -2147483648); + +// Printing 32-bit unsigned integers +puts("%x %x %X %X\n", 2147483647, 1234, 2147483647, 1234); +// Output: 7fffffff 4d2 7FFFFFFF 4D2 + +// Printing unsigned long long integers in hex +puts("%lX %lx\n", 0x123456789ABCDEF0, 9223372036854775809); +// Output: 123456789ABCDEF0 8000000000000001 + +// Printing a character +puts("Name: %c\n", 'b'); + +// Printing a string +puts("Hello %s\n", "World"); + +// Printing a '%' +puts("100%%\n"); +\end{lstlisting} + +\end{document} diff --git a/doc/figures/bootedKernel.png b/doc/figures/bootedKernel.png index 8103eb6..6d17de6 100644 Binary files a/doc/figures/bootedKernel.png and b/doc/figures/bootedKernel.png differ diff --git a/doc/preamble/macros.tex b/doc/preamble/macros.tex index 2bcba7e..854c22c 100644 --- a/doc/preamble/macros.tex +++ b/doc/preamble/macros.tex @@ -109,4 +109,4 @@ }{% \end{quote}\par\medskip } -\makeatother \ No newline at end of file +\makeatother diff --git a/include/printf.h b/include/printf.h index 80ebcd1..866f601 100644 --- a/include/printf.h +++ b/include/printf.h @@ -1,16 +1,20 @@ #ifndef PRINTF_H #define PRINTF_H +#include #include +#include #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif -void putc(char c); -void puts(const char *s); -char getc(void); -void getlines(char *restrict buffer, size_t length); + void putc(char c); + void puts(const char *s); + void printf(char *s, ...); + char getc(void); + void getlines(char *restrict buffer, size_t length); #ifdef __cplusplus } diff --git a/kernel/kernel.c b/kernel/kernel.c index 8a528ec..6c725dd 100644 --- a/kernel/kernel.c +++ b/kernel/kernel.c @@ -1,4 +1,5 @@ #include +#include #include "printf.h" #include "clear.h" @@ -14,8 +15,7 @@ static const char *banner[] = { "\r\n", "Welcome to your own little Astra world!\r\n", "Type away, explore, have fun.\r\n", - "\r\n" -}; + "\r\n"}; // Initializes and prints the welcome banner. static void init_message(void) @@ -32,9 +32,9 @@ void kernel_main(void) clear(); init_message(); - puts("AstraKernel is running...\r\n"); - puts("Press Ctrl-A and then X to exit QEMU.\r\n"); - puts("\r\n"); + printf("AstraKernel is running...\r\n"); + printf("Press Ctrl-A and then X to exit QEMU.\r\n"); + printf("\r\n"); char input_buffer[100]; @@ -42,31 +42,40 @@ void kernel_main(void) while (is_running) { input_buffer[0] = '\0'; // Clear the input buffer - puts("AstraKernel > "); + printf("AstraKernel > "); getlines(input_buffer, sizeof(input_buffer)); - - puts("\r\n"); + + printf("\r\n"); switch (input_buffer[0]) { case 'h': // Check for help command - puts("\nHelp: Press 'q' to exit, 'h' for help.\r\n"); + printf("\nHelp:\n 'q' to exit\n 'h' for help\n 'c' to clear screen\n 't' to print current time\n 'd' to print current date\r\n"); + break; + case 'e': // TODO: This is for testing purposes. Remove once not needed + printf("%ld %ld %ld\n", 0, -9223372036854775808, 9223372036854775807); + printf("%d %d\n", 2147483647, -2147483648); + printf("%x %lx %lX %X\n", 2147483647, 2147483649, 2147483648, 1234); + printf("%lX %x %lx\n", 0x123456789abcdef0, 1234, 9223372036854775809); + printf("Name: %c\n", 'b'); + printf("Hello %s\n", "World"); + printf("100%%\n"); break; case 'q': // Check for exit command - puts("Exiting...\r\n"); + printf("Exiting...\r\n"); is_running = false; break; - case 'c': // Cjeck for clear screen command + case 'c': // Check for clear screen command clear(); break; - case 't': // Check for time command - puts("Current time: 12:00 PM\r\n"); // TO-DO: Implement real time check + case 't': // Check for time command + printf("Current time: 12:00 PM\r\n"); // TO-DO: Implement real time check break; - case 'd': // Check for date command - puts("Current date: 2023-10-01\r\n"); // TO-DO: Implement real date check + case 'd': // Check for date command + printf("Current date: 2023-10-01\r\n"); // TO-DO: Implement real date check break; default: - puts("Unknown command. Type 'h' for help.\r\n"); + printf("Unknown command. Type 'h' for help.\r\n"); break; } } diff --git a/user/clear.c b/user/clear.c index 885114f..70a700a 100644 --- a/user/clear.c +++ b/user/clear.c @@ -3,6 +3,6 @@ // Clears the terminal screen and moves the cursor to the home position. void clear(void) { - puts("\x1B[2J"); - puts("\x1B[H"); + printf("\x1B[2J"); + printf("\x1B[H"); } \ No newline at end of file diff --git a/user/printf.c b/user/printf.c index 0c0cc47..0b04f45 100644 --- a/user/printf.c +++ b/user/printf.c @@ -1,6 +1,18 @@ #include +#include +#include #include +// TODO: Check working of printf, all cases + +typedef struct Format_State +{ + unsigned long long num; + bool valid_format; + bool in_format; // Used to handle multi-character format specifiers + bool long_format; // %l. type specifier +} Format_State; + _Static_assert(sizeof(uint32_t) == 4, "uint32_t must be 4 bytes"); // Memory-mapped I/O registers for UART0 on QEMU versatilepb @@ -19,6 +31,124 @@ static inline void putc(char c) UART0_DR = (uint32_t)c; } +unsigned long long _bdiv(unsigned long long dividend, unsigned long long divisor, unsigned long long *remainder) +{ + // INFO: Currently, this algorithm involves dividing only by 10 and 16. So, division by zero should not be a problem, yet. + // TO-DO: Design a faster division algorithm and ensure that division by zero is not allowed. + *remainder = 0; + unsigned long long quotient = 0; + + for (int i = 63; i >= 0; i--) + { + quotient <<= 1; + *remainder <<= 1; + unsigned long long temp = (unsigned long long)1 << i; // Without this cast, the type is misinterpreted leading to UB + *remainder |= (dividend & temp) >> i; + + if (*remainder >= divisor) + { + *remainder -= divisor; + quotient |= 1; + } + } + + return quotient; +} + +void _putunsignedlong(unsigned long long unum, unsigned long long base, bool hex_capital) +{ + char out_buf[32]; + uint32_t len = 0; + + char base16_factor = (7 * (hex_capital) + 39 * (!hex_capital)) * (base == 16); // If base 16, add 7 or 39 depending on + // X or x respectively + + unsigned long long mod; + unsigned long long res; + + do + { + res = _bdiv(unum, base, &mod); + out_buf[len] = '0' + mod + base16_factor * (mod > 9); + + len++; + unum = res; + } while (unum); + + for (uint32_t i = len; i > 0; i--) + { + putc(out_buf[i - 1]); + } +} + +void _putunsignedint(uint32_t unum) +{ + _putunsignedlong(unum, 10, false); +} + +void _puthexsmall(unsigned long long unum) +{ + _putunsignedlong(unum, 16, false); +} + +void _puthexcapital(unsigned long long unum) +{ + _putunsignedlong(unum, 16, true); +} + +void _putintegers(char control, Format_State *format_state) +{ + format_state->in_format = false; // Valid as only %l updates this + + switch (control) + { + case 'u': + { + _putunsignedlong(format_state->num, 10, false); + break; + } + + case 'd': + { + uint32_t shamt = format_state->long_format * 63 + !(format_state->long_format) * 31; + + uint32_t sign_bit = (format_state->num) >> shamt; + + if (sign_bit) + { + putc('-'); + + format_state->num = ((format_state->num ^ 0xffffffff) * !format_state->long_format + // 2's complement for 32 bit + (format_state->num ^ 0xffffffffffffffff) * format_state->long_format) + // 2's complement for 64 bit + 1; + } + + _putunsignedlong(format_state->num, 10, false); + + break; + } + case 'x': + { + _puthexsmall(format_state->num); + break; + } + case 'X': + { + _puthexcapital(format_state->num); + break; + } + } +} + +// Validation for integer formats +bool _validate_format_specifier(char c) +{ + return (c == 'd') || + (c == 'u') || + (c == 'x') || + (c == 'X'); +} + // Send a null-terminated string over UART void puts(const char *s) { @@ -28,6 +158,94 @@ void puts(const char *s) } } +// Send a formatted string over UART +void printf(char *s, ...) +{ + va_list elem_list; + + va_start(elem_list, s); + + Format_State format_state = {.num = 0, .valid_format = false, .in_format = false, .long_format = false}; + + while (*s) + { + if (*s == '%' || format_state.in_format) + { + switch (*(s + 1)) + { + case 'c': + { + uint32_t character = va_arg(elem_list, uint32_t); // Characters are converted into 'int' when passed as var-args + putc((char)character); + break; + } + case 's': + { + char *it = va_arg(elem_list, char *); + + if (it == NULL) + { + printf("(null)"); + break; + } + + while (*it) + { + putc(*it++); + } + break; + } + case 'l': + { + format_state.in_format = true; + format_state.long_format = true; + s += 1; // Evaluate the immediate next char + continue; + } + case '%': + { + putc('%'); + break; + } + default: + { + format_state.valid_format = _validate_format_specifier(*(s + 1)); + + if (format_state.valid_format) + { + if (format_state.long_format) + { + format_state.num = (unsigned long long)va_arg(elem_list, unsigned long long); + } + else + { + format_state.num = (uint32_t)va_arg(elem_list, uint32_t); + } + + _putintegers(*(s + 1), &format_state); + + format_state.long_format = false; + } + else + { + // TODO: Implement invalid format error handling here + } + + break; + } + } + + s += 2; // Skip format specifier + } + else + { + putc(*s++); + } + } + + va_end(elem_list); +} + // Function to get user input from UART static inline char getc(void) { @@ -58,9 +276,9 @@ void getlines(char *restrict buffer, size_t length) } else { - buffer[index++] = character; // Store the character in the buffer - putc(character); // Echo the character back + buffer[index++] = character; // Store the character in the buffer + putc(character); // Echo the character back } } buffer[index] = '\0'; // Null-terminate the string -} \ No newline at end of file +}