From ad2204cab2165efecdb735493713011bf31f0e0d Mon Sep 17 00:00:00 2001 From: Edward Dean Date: Wed, 22 May 2019 20:49:27 +0100 Subject: [PATCH] Add TTY and VGA interface (#20) Line endings Removed redundant files Line ending --- build.zig | 1 + src/kernel/arch.zig | 30 + src/kernel/arch/x86/arch.zig | 69 +- src/kernel/kmain.zig | 94 +- src/kernel/tty.zig | 2475 ++++++++++++++++++++++++++++++++++ src/kernel/vga.zig | 413 ++++++ test/kernel/arch_mock.zig | 32 + 7 files changed, 3016 insertions(+), 98 deletions(-) create mode 100644 src/kernel/tty.zig create mode 100644 src/kernel/vga.zig create mode 100644 test/kernel/arch_mock.zig diff --git a/build.zig b/build.zig index a4f867d..1c5934b 100644 --- a/build.zig +++ b/build.zig @@ -53,6 +53,7 @@ fn buildTest(b: *Builder, src_path: []const u8) void { var file_src = concat(b.allocator, src_path2.toSlice(), file) catch unreachable; file_src.append(".zig") catch unreachable; const tst = b.addTest(file_src.toSlice()); + tst.setMainPkgPath("."); step.dependOn(&tst.step); } } diff --git a/src/kernel/arch.zig b/src/kernel/arch.zig index 6f9f2cd..2949827 100644 --- a/src/kernel/arch.zig +++ b/src/kernel/arch.zig @@ -1,2 +1,32 @@ +// Zig version: 0.4.0 + +/// /// Initialise the architecture +/// pub extern fn init() void; + +/// +/// Inline assembly to write to a given port with a byte of data. +/// +/// Arguments: +/// IN port: u16 - The port to write to. +/// IN data: u8 - The byte of data that will be sent. +/// +pub extern fn outb(port: u16, data: u8) void; + +/// +/// Inline assembly that reads data from a given port and returns its value. +/// +/// Arguments: +/// IN port: u16 - The port to read data from. +/// +/// Return: +/// The data that the port returns. +/// +pub extern fn inb(port: u16) u8; + +/// +/// A simple way of waiting for I/O event to happen by doing an I/O event to flush the I/O +/// event being waited. +/// +pub extern fn ioWait() void; diff --git a/src/kernel/arch/x86/arch.zig b/src/kernel/arch/x86/arch.zig index 5aaee35..bf9981b 100644 --- a/src/kernel/arch/x86/arch.zig +++ b/src/kernel/arch/x86/arch.zig @@ -1,3 +1,17 @@ +// Zig version: 0.4.0 + +const is_test = @import("builtin").is_test; + +const ALIGN = 1 << 0; +const MEMINFO = 1 << 1; +const MAGIC = 0x1BADB002; +const FLAGS = ALIGN | MEMINFO; + +const KERNEL_ADDR_OFFSET = 0xC0000000; +const KERNEL_PAGE_NUMBER = KERNEL_ADDR_OFFSET >> 22; +// The number of pages occupied by the kernel, will need to be increased as we add a heap etc. +const KERNEL_NUM_PAGES = 1; + extern fn kmain() void; const MultiBoot = packed struct { @@ -6,22 +20,12 @@ const MultiBoot = packed struct { checksum: i32, }; -const ALIGN = 1 << 0; -const MEMINFO = 1 << 1; -const MAGIC = 0x1BADB002; -const FLAGS = ALIGN | MEMINFO; - -export var multiboot align(4) linksection(".rodata.boot") = MultiBoot{ +export var multiboot align(4) linksection(".rodata.boot") = MultiBoot { .magic = MAGIC, .flags = FLAGS, .checksum = -(MAGIC + FLAGS), }; -const KERNEL_ADDR_OFFSET = 0xC0000000; -const KERNEL_PAGE_NUMBER = KERNEL_ADDR_OFFSET >> 22; -// The number of pages occupied by the kernel, will need to be increased as we add a heap etc. -const KERNEL_NUM_PAGES = 1; - // The initial page directory used for booting into the higher half. Should be overwritten later export var boot_page_directory: [1024]u32 align(4096) linksection(".rodata.boot") = init: { // Increase max number of branches done by comptime evaluator @@ -35,7 +39,7 @@ export var boot_page_directory: [1024]u32 align(4096) linksection(".rodata.boot" var i = 0; var idx = 1; - // Fill preceding pages with zeroes. May be unecessary but incurs no runtime cost + // Fill preceding pages with zeroes. May be unnecessary but incurs no runtime cost while (i < KERNEL_PAGE_NUMBER - 1) : ({ i += 1; idx += 1; @@ -51,7 +55,7 @@ export var boot_page_directory: [1024]u32 align(4096) linksection(".rodata.boot" }) { dir[idx] = 0x00000083 | (i << 22); } - // Fill suceeding pages with zeroes. May be unecessary but incurs no runtime cost + // Fill succeeding pages with zeroes. May be unnecessary but incurs no runtime cost i = 0; while (i < 1024 - KERNEL_PAGE_NUMBER - KERNEL_NUM_PAGES) : ({ i += 1; @@ -107,4 +111,43 @@ export nakedcc fn start_higher_half() noreturn { while (true) {} } +/// +/// Initialise the architecture +/// export fn init() void {} + +/// +/// Inline assembly to write to a given port with a byte of data. +/// +/// Arguments: +/// IN port: u16 - The port to write to. +/// IN data: u8 - The byte of data that will be sent. +/// +export fn outb(port: u16, data: u8) void { + asm volatile ("outb %[data], %[port]" + : + : [port] "{dx}" (port), [data] "{al}" (data)); +} + +/// +/// Inline assembly that reads data from a given port and returns its value. +/// +/// Arguments: +/// IN port: u16 - The port to read data from. +/// +/// Return: +/// The data that the port returns. +/// +export fn inb(port: u16) u8 { + return asm volatile ("inb %[port], %[result]" + : [result] "={al}" (-> u8) + : [port] "N{dx}" (port)); +} + +/// +/// A simple way of waiting for I/O event to happen by doing an I/O event to flush the I/O +/// event being waited. +/// +export fn ioWait() void { + outb(0x80, 0); +} diff --git a/src/kernel/kmain.zig b/src/kernel/kmain.zig index 4659112..a75270e 100644 --- a/src/kernel/kmain.zig +++ b/src/kernel/kmain.zig @@ -1,17 +1,14 @@ -// -// kmain -// Zig version: -// Author: DrDeano -// Date: 2019-03-30 -// +// Zig version: 0.4.0 + const builtin = @import("builtin"); -const arch = @import("arch.zig"); +const arch = if (builtin.is_test) @import("../../test/kernel/arch_mock.zig") else @import("arch.zig"); const multiboot = @import("multiboot.zig"); +const tty = @import("tty.zig"); +const vga = @import("vga.zig"); pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn { @setCold(true); - terminal.write("KERNEL PANIC: "); - terminal.write(msg); + tty.print("\nKERNEL PANIC: {}\n", msg); while (true) {} } @@ -19,81 +16,8 @@ pub export fn kmain(mb_info: *multiboot.multiboot_info_t, mb_magic: u32) void { if (mb_magic == multiboot.MULTIBOOT_BOOTLOADER_MAGIC) { // Booted with compatible bootloader arch.init(); - terminal.initialize(); - terminal.write("Hello, kernel World!"); + vga.init(); + tty.init(); + tty.print("\nHello Pluto from kernel :)\n"); } } - -// Hardware text mode color constants -const VGA_COLOUR = enum(u8) { - VGA_COLOUR_BLACK, - VGA_COLOUR_BLUE, - VGA_COLOUR_GREEN, - VGA_COLOUR_CYAN, - VGA_COLOUR_RED, - VGA_COLOUR_MAGENTA, - VGA_COLOUR_BROWN, - VGA_COLOUR_LIGHT_GREY, - VGA_COLOUR_DARK_GREY, - VGA_COLOUR_LIGHT_BLUE, - VGA_COLOUR_LIGHT_GREEN, - VGA_COLOUR_LIGHT_CYAN, - VGA_COLOUR_LIGHT_RED, - VGA_COLOUR_LIGHT_MAGENTA, - VGA_COLOUR_LIGHT_BROWN, - VGA_COLOUR_WHITE, -}; - -fn vga_entry_colour(fg: VGA_COLOUR, bg: VGA_COLOUR) u8 { - return @enumToInt(fg) | (@enumToInt(bg) << 4); -} - -fn vga_entry(uc: u8, colour: u8) u16 { - return u16(uc) | (u16(colour) << 8); -} - -const VGA_WIDTH = 80; -const VGA_HEIGHT = 25; - -const terminal = struct { - var row = usize(0); - var column = usize(0); - var colour = vga_entry_colour(VGA_COLOUR.VGA_COLOUR_LIGHT_GREY, VGA_COLOUR.VGA_COLOUR_BLACK); - - const buffer = @intToPtr([*]volatile u16, 0xC00B8000); - - fn initialize() void { - var y = usize(0); - while (y < VGA_HEIGHT) : (y += 1) { - var x = usize(0); - while (x < VGA_WIDTH) : (x += 1) { - putCharAt(' ', colour, x, y); - } - } - } - - fn setColour(new_colour: u8) void { - colour = new_colour; - } - - fn putCharAt(c: u8, new_colour: u8, x: usize, y: usize) void { - const index = y * VGA_WIDTH + x; - buffer[index] = vga_entry(c, new_colour); - } - - fn putChar(c: u8) void { - putCharAt(c, colour, column, row); - column += 1; - if (column == VGA_WIDTH) { - column = 0; - row += 1; - if (row == VGA_HEIGHT) - row = 0; - } - } - - fn write(data: []const u8) void { - for (data) |c| - putChar(c); - } -}; diff --git a/src/kernel/tty.zig b/src/kernel/tty.zig new file mode 100644 index 0000000..9e879ed --- /dev/null +++ b/src/kernel/tty.zig @@ -0,0 +1,2475 @@ +// Zig version: 0.4.0 + +const vga = @import("vga.zig"); + +const expectEqual = @import("std").testing.expectEqual; +const expectError = @import("std").testing.expectError; +const expect = @import("std").testing.expect; +const warn = @import("std").debug.warn; +const fmt = @import("std").fmt; + +/// The number of rows down from the top (row 0) where the displayable region starts. Above is +/// where the logo and time is printed +const ROW_MIN: u16 = 7; + +/// The total number of rows in the displayable region +const ROW_TOTAL: u16 = vga.HEIGHT - ROW_MIN; + +/// The total number of pages (static) that the terminal will remember. In the future, this can +/// move to a more dynamic allocation when a kheap is implemented. +const TOTAL_NUM_PAGES: u16 = 5; + +/// The total number of VGA (or characters) elements are on a page +const TOTAL_CHAR_ON_PAGE: u16 = vga.WIDTH * ROW_TOTAL; + +/// The start of the displayable region in the video buffer memory +const START_OF_DISPLAYABLE_REGION: u16 = vga.WIDTH * ROW_MIN; + +/// The total number of VGA elements (or characters) the video buffer can display +const VIDEO_BUFFER_SIZE: u16 = vga.WIDTH * vga.HEIGHT; + +const PrintError = error { + OutOfBounds, +}; + +/// The current x position of the cursor. +var column: u8 = 0; + +/// The current y position of the cursor. +var row: u8 = 0; + +/// The current colour of the display with foreground and background colour. +var colour: u8 = undefined; + +/// The buffer starting from the beginning of the video memory location that contains all data +/// written to the display. +var video_buffer: []volatile u16 = undefined; + +/// The blank VGA entry to be used to clear the screen. +var blank: u16 = undefined; + +/// A total of TOTAL_NUM_PAGES pages that can be saved and restored to from and to the video buffer +var pages: [TOTAL_NUM_PAGES][TOTAL_CHAR_ON_PAGE]u16 = init: { + //@setEvalBranchQuota(TOTAL_NUM_PAGES * TOTAL_CHAR_ON_PAGE + TOTAL_CHAR_ON_PAGE); + var p: [TOTAL_NUM_PAGES][TOTAL_CHAR_ON_PAGE]u16 = undefined; + + for (p) |*page| { + page.* = []u16{0} ** TOTAL_CHAR_ON_PAGE; + } + + break :init p; +}; + +/// The current page index. +var page_index: u8 = 0; + +/// +/// Copies data into the video buffer. This is used for copying a page into the video buffer. +/// +/// Global changes: +/// video_buffer +/// +/// Arguments: +/// IN video_buf_offset: u16 - The offset into the video buffer to start copying to. +/// IN data: []const u16 - The data to copy into the video buffer. +/// IN size: u16 - The amount to copy. +/// +/// Errors: +/// PrintError.OutOfBounds - If offset or the size to copy is greater than the size of the +/// video buffer or data to copy. +/// +fn videoCopy(video_buf_offset: u16, data: []const u16, size: u16) PrintError!void { + // Secure programming ;) + if (video_buf_offset >= video_buffer.len and + size > video_buffer.len - video_buf_offset and + size > data.len) { + return PrintError.OutOfBounds; + } + + var i: usize = 0; + while (i < size) : (i += 1) { + video_buffer[video_buf_offset + i] = data[i]; + } +} + +/// +/// Moves data with a page without overriding itself. +/// +/// Arguments: +/// IN dest: []u16 - The destination position to copy into. +/// IN src: []u16 - The source position to copy from. +/// IN size: u16 - The amount to copy. +/// +/// Errors: +/// PrintError.OutOfBounds - If the size to copy is greater than the size of +/// pages. +/// +fn pageMove(dest: []u16, src: []u16, size: usize) PrintError!void { + if (dest.len < size or src.len < size) { + return PrintError.OutOfBounds; + } + + // Not an error is size is zero, nothing will be copied + if (size == 0) return; + + // Make sure we don't override the values we want to copy + if (@ptrToInt(&dest[0]) < @ptrToInt(&src[0])) { + var i: usize = 0; + while (i != size) : (i += 1) { + dest[i] = src[i]; + } + } else { + var i: usize = size; + while (i != 0) { + i -= 1; + dest[i] = src[i]; + } + } +} + +/// +/// Clears a region of the video buffer to a VGA entry from the beginning. +/// +/// Global changes: +/// video_buffer +/// +/// Arguments: +/// IN c: u16 - VGA entry to set the video buffer to. +/// IN size: u16 - The number to VGA entries to set from the beginning of the video buffer. +/// +fn setVideoBuffer(c: u16, size: u16) void { + for (video_buffer[0..size]) |*b| { + b.* = c; + } +} + +/// +/// Updated the hardware cursor to the current column and row (x, y). +/// +fn updateCursor() void { + vga.updateCursor(column, row); +} + +/// +/// Get the hardware cursor to set the current column and row (x, y). +/// +/// Global changes: +/// column +/// row +/// +fn getCursor() void { + var cursor: u16 = vga.getCursor(); + + row = @truncate(u8, cursor / vga.WIDTH); + column = @truncate(u8, cursor % vga.WIDTH); +} + +/// +/// Display the current page number at the bottom right corner. +/// +/// Global changes: +/// video_buffer +/// pages +/// +fn displayPageNumber() void { + const column_temp: u8 = column; + const row_temp: u8 = row; + + // A bit hard coded as the kprintf below is 11 characters long. + // Once got a ksnprintf, then can use that value to set how far from the end to print. + column = vga.WIDTH - 11; + row = ROW_MIN - 1; + + print("Page {} of {}", page_index, TOTAL_NUM_PAGES - 1); + + column = column_temp; + row = row_temp; +} + +/// +/// Put a character at a specific column and row position on the screen. This will use the current +/// colour. +/// +/// Global changes: +/// video_buffer +/// pages +/// page_index +/// +/// Arguments: +/// IN char: u8 - The character to print. This will be combined with the current colour. +/// IN x: u8 - The x position (column) to put the character at. +/// IN y: u8 - The y position (row) to put the character at. +/// +/// Errors: +/// PrintError.OutOfBounds - If trying to print outside the video buffer +/// +fn putEntryAt(char: u8, x: u8, y: u8) PrintError!void { + const index: u16 = y * vga.WIDTH + x; + + // Bounds check + if (index >= VIDEO_BUFFER_SIZE) { + return PrintError.OutOfBounds; + } + + const char_entry: u16 = vga.entry(char, colour); + + if (index >= START_OF_DISPLAYABLE_REGION) { + // If not at page zero, (bottom of page), then display that page + // The user has move up a number of pages and then typed a letter, so need to move to the + // 0'th page + if (page_index != 0) { + // This isn't out of bounds + page_index = 0; + videoCopy(START_OF_DISPLAYABLE_REGION, pages[page_index][0..TOTAL_CHAR_ON_PAGE], TOTAL_CHAR_ON_PAGE) catch unreachable; + displayPageNumber(); + + // If not on page 0, then the cursor would have been disabled + vga.enableCursor(); + updateCursor(); + } + pages[page_index][index - START_OF_DISPLAYABLE_REGION] = char_entry; + } + video_buffer[index] = char_entry; +} + +/// Move rows up pages across multiple pages leaving the last rows blank. +/// +/// Global changes: +/// pages +/// +/// Arguments: +/// IN rows: u16 - The number of rows to move up. +/// +/// Errors: +/// PrintError.OutOfBounds - If trying to move up more rows on a page +/// +fn pagesMoveRowsUp(rows: u16) PrintError!void { + // Out of bounds check, also no need to move 0 rows + if (rows > ROW_TOTAL) { + return PrintError.OutOfBounds; + } + + // Not an error to move 0 rows, but is pointless + if (rows == 0) return; + + // Move up rows in last page up by "rows" + const row_length: u16 = rows * vga.WIDTH; + const chars_to_move: u16 = (ROW_TOTAL - rows) * vga.WIDTH; + pageMove(pages[TOTAL_NUM_PAGES - 1][0..chars_to_move], pages[TOTAL_NUM_PAGES - 1][row_length..], chars_to_move) catch unreachable; + + // Loop for the other pages + var i = TOTAL_NUM_PAGES - 1; + while (i > 0) : (i -= 1) { + pageMove(pages[i][chars_to_move..], pages[i - 1][0..row_length], row_length) catch unreachable; + pageMove(pages[i - 1][0..chars_to_move], pages[i - 1][row_length..], chars_to_move) catch unreachable; + } + + // Clear the last lines + for (pages[0][chars_to_move..]) |*p| { + p.* = blank; + } +} + +/// +/// When the text/terminal gets to the bottom of the screen, then move all line up by the amount +/// that are below the bottom of the screen. Usually moves up by one line. +/// +/// Global changes: +/// pages +/// video_buffer +/// row +/// +fn scroll() void { + // Added the condition in the if from pagesMoveRowsUp as don't need to move all rows + if (row >= vga.HEIGHT and (row - vga.HEIGHT + 1) <= ROW_TOTAL) { + var rows_to_move: u16 = row - vga.HEIGHT + 1; + + // Move rows up pages by temp, will usually be one. + // Can't return an error + pagesMoveRowsUp(rows_to_move) catch unreachable; + + // Move all rows up by rows_to_move + var i: u16 = 0; + while (i < (ROW_TOTAL - rows_to_move) * vga.WIDTH) : (i += 1) { + video_buffer[START_OF_DISPLAYABLE_REGION + i] = video_buffer[(rows_to_move * vga.WIDTH) + START_OF_DISPLAYABLE_REGION + i]; + } + + // Set the last rows to blanks + i = 0; + while (i < vga.WIDTH * rows_to_move) : (i += 1) { + video_buffer[(vga.HEIGHT - rows_to_move) * vga.WIDTH + i] = blank; + } + + row = vga.HEIGHT - 1; + } +} + +/// +/// Print a character without updating the cursor. For speed when printing a string as only need to +/// update the cursor once. This will also print the special characters: \n, \r, \t and \b. (\b is +/// not a valid character so use \x08 which is the hex value). +/// +/// Global changes: +/// pages +/// video_buffer +/// row +/// column +/// +/// Arguments: +/// IN char: u8 - The character to print. +/// +/// Errors: +/// PrintError.OutOfBounds - If trying to scroll more rows on a page/displayable region or +/// print beyond the video buffer. +/// +fn putChar(char: u8) PrintError!void { + const column_temp: u8 = column; + const row_temp: u8 = row; + + // If there was an error, then set the row and column back to where is was + // Like nothing happened + errdefer column = column_temp; + errdefer row = row_temp; + + switch (char) { + '\n' => { + column = 0; + row += 1; + scroll(); + }, + '\t' => { + column += 4; + if (column >= vga.WIDTH) { + column -= @truncate(u8, vga.WIDTH); + row += 1; + scroll(); + } + }, + '\r' => { + column = 0; + }, + // \b + '\x08' => { + if (column == 0) { + if (row != 0) { + column = vga.WIDTH - 1; + row -= 1; + } + } else { + column -= 1; + } + }, + else => { + try putEntryAt(char, column, row); + column += 1; + if (column == vga.WIDTH) { + column = 0; + row += 1; + scroll(); + } + }, + } +} + +/// +/// Print a string to the TTY. This also updates to hardware cursor. +/// +/// Global changes: +/// video_buffer +/// pages +/// column +/// row +/// +/// Arguments: +/// IN str: []const u8 - The string to print. +/// +/// Errors: +/// PrintError.OutOfBounds - If trying to print beyond the video buffer. +/// +fn writeString(str: []const u8) PrintError!void { + // Make sure we update the cursor to the last character + defer updateCursor(); + for (str) |char| { + try putChar(char); + } +} + +/// +/// A call back function for use in the formation of a string. This calls writeString normally. +/// +/// Global changes: +/// video_buffer +/// pages +/// column +/// row +/// +/// Arguments: +/// IN context: void - The context of the printing. This will be empty. +/// IN string: []const u8 - The string to print. +/// +/// Errors: +/// PrintError.OutOfBounds - If trying to print beyond the video buffer. +/// +fn printCallback(context: void, string: []const u8) PrintError!void { + try writeString(string); +} + +/// +/// Print a character without updating the cursor. For speed when printing a string as only need to +/// update the cursor once. This will also print the special characters: \n, \r, \t and \b +/// +/// Global changes: +/// video_buffer +/// +/// Arguments: +/// IN char: u8 - The character to print. +/// +fn printLogo() void { + const column_temp = column; + const row_temp = row; + + column = 0; + row = 0; + + writeString(" _____ _ _ _ _______ ____ \n") catch unreachable; + writeString(" | __ \\ | | | | | | |__ __| / __ \\ \n") catch unreachable; + writeString(" | |__) | | | | | | | | | | | | |\n") catch unreachable; + writeString(" | ___/ | | | | | | | | | | | |\n") catch unreachable; + writeString(" | | | |____ | |__| | | | | |__| |\n") catch unreachable; + writeString(" |_| |______| \\____/ |_| \\____/ \n") catch unreachable; + + column = column_temp; + row = row_temp; +} + +/// +/// Print a formatted string to the terminal in the current colour. This used the standard zig +/// formatting. +/// +/// Global changes: +/// pages +/// video_buffer +/// column +/// row +/// +/// Arguments: +/// IN format: []const u8 - The format string to print +/// IN args: ... - The arguments to be used in the formatted string +/// +pub fn print(comptime format: []const u8, args: ...) void { + // Printing can't error because of the scrolling, if it does, we have a big problem + fmt.format({}, PrintError, printCallback, format, args) catch unreachable; +} + +/// +/// Move up a page. This will copy the page above to the video buffer. Will keep trace of which +/// page is being displayed. +/// +/// Global changes: +/// video_buffer +/// pages +/// page_index +/// +pub fn pageUp() void { + if (page_index < TOTAL_NUM_PAGES - 1) { + // Copy page to display + page_index += 1; + videoCopy(START_OF_DISPLAYABLE_REGION, pages[page_index][0..TOTAL_CHAR_ON_PAGE], TOTAL_CHAR_ON_PAGE) catch unreachable; + displayPageNumber(); + vga.disableCursor(); + } +} + +/// +/// Move down a page. This will copy the page bellow to the video buffer. Will keep trace of which +/// page is being displayed. +/// +/// Global changes: +/// video_buffer +/// pages +/// page_index +/// +pub fn pageDown() void { + if (page_index > 0) { + // Copy page to display + page_index -= 1; + videoCopy(START_OF_DISPLAYABLE_REGION, pages[page_index][0..TOTAL_CHAR_ON_PAGE], TOTAL_CHAR_ON_PAGE) catch unreachable; + displayPageNumber(); + if(page_index == 0) { + vga.enableCursor(); + updateCursor(); + } else { + vga.disableCursor(); + } + } +} + +/// +/// This clears the entire screen with blanks using the current colour. This will also save the +/// screen to the pages so can scroll back down. +/// +/// Global changes: +/// video_buffer +/// +pub fn clearScreen() void { + // Move all the rows up + //row = ROW_MIN - 1 + vga.HEIGHT; // 42 + pagesMoveRowsUp(ROW_TOTAL) catch unreachable; + + // Clear the screen + var i: u16 = START_OF_DISPLAYABLE_REGION; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + video_buffer[i] = blank; + } + column = 0; + row = ROW_MIN; + updateCursor(); +} + +/// +/// This moves the software and hardware cursor to the left by one. +/// +/// Global changes: +/// column +/// row +/// +pub fn moveCursorLeft() void { + if (column == 0) { + if (row != 0) { + column = vga.WIDTH - 1; + row -= 1; + } + } else { + column -= 1; + } + updateCursor(); +} + +/// +/// This moves the software and hardware cursor to the right by one. +/// +/// Global changes: +/// column +/// row +/// +pub fn moveCursorRight() void { + if (column == (vga.WIDTH - 1)) { + if (row != (vga.HEIGHT - 1)) { + column = 0; + row += 1; + } + } else { + column += 1; + } + updateCursor(); +} + +/// +/// This will set a new colour for the screen. It will only become effective when printing new +/// characters. Use vga.colourEntry and the colour enums to set the colour. +/// +/// Global changes: +/// colour +/// blank +/// +/// Arguments: +/// new_colour: u8 - The new foreground and background colour of the screen. +/// +pub fn setColour(new_colour: u8) void { + colour = new_colour; + blank = vga.entry(0, colour); +} + +/// +/// Initialise the tty. This will keep the bootloaders output and set the software cursor to where +/// the bootloader left it. Will copy the current screen to the pages, set the colour and blank +/// entry, print the logo and display the 0'th page. +/// +/// Global changes: +/// pages +/// video_buffer +/// column +/// row +/// colour +/// blank +/// +pub fn init() void { + // Video buffer in higher half + video_buffer = @intToPtr([*]volatile u16, 0xC00B8000)[0..VIDEO_BUFFER_SIZE]; + setColour(vga.entryColour(vga.COLOUR_LIGHT_GREY, vga.COLOUR_BLACK)); + + // Enable and get the hardware cursor to set the software cursor + vga.enableCursor(); + getCursor(); + + if (row != 0 or column != 0) { + // Copy rows 7 down to make room for logo + // If there isn't enough room, only take the bottom rows + var row_offset: u16 = 0; + if(vga.HEIGHT - 1 - row < ROW_MIN) { + row_offset = u16(ROW_MIN - (vga.HEIGHT - 1 - row)); + } + + // Make a copy into the terminal_pages + // Assuming that there is only one page + var i: u16 = 0; + while (i < row * vga.WIDTH) : (i += 1) { + pages[0][i] = video_buffer[i]; + } + + // Move terminal_row rows down 7 + i = 0; + if (@ptrToInt(&video_buffer[ROW_MIN * vga.WIDTH]) < @ptrToInt(&video_buffer[row_offset * vga.WIDTH])) { + while (i != row * vga.WIDTH) : (i += 1) { + video_buffer[i + (ROW_MIN * vga.WIDTH)] = video_buffer[i + (row_offset * vga.WIDTH)]; + } + } else { + i = row * vga.WIDTH; + while (i != 0) { + i -= 1; + video_buffer[i + (ROW_MIN * vga.WIDTH)] = video_buffer[i + (row_offset * vga.WIDTH)]; + } + } + + // Set the top 7 rows blank + setVideoBuffer(blank, START_OF_DISPLAYABLE_REGION); + row += @truncate(u8, row_offset + ROW_MIN); + } else { + // Clear the screen + setVideoBuffer(blank, VIDEO_BUFFER_SIZE); + } + + printLogo(); + displayPageNumber(); +} + +fn resetGlobals() void { + column = 0; + row = 0; + page_index = 0; + colour = undefined; + video_buffer = @intToPtr([*]volatile u16, 0xB8000)[0..VIDEO_BUFFER_SIZE]; + blank = undefined; + + pages = init: { + var p: [TOTAL_NUM_PAGES][TOTAL_CHAR_ON_PAGE]u16 = undefined; + + for (p) |*page| { + page.* = []u16{0} ** TOTAL_CHAR_ON_PAGE; + } + + break :init p; + }; +} + +const test_colour: u8 = vga.entryColour(vga.COLOUR_LIGHT_GREY, vga.COLOUR_BLACK); +var test_video_buffer = []volatile u16{0} ** VIDEO_BUFFER_SIZE; + +fn _setVideoBuffer() void { + // Change to a stack location + video_buffer = test_video_buffer[0..VIDEO_BUFFER_SIZE]; + + expectEqual(@ptrToInt(video_buffer.ptr), @ptrToInt(&test_video_buffer[0])); + expect(@typeOf(video_buffer) == []volatile u16); + + setColour(test_colour); + + // Set pages to blank + var i: u16 = 0; + while (i < TOTAL_NUM_PAGES) : (i += 1) { + var j: u16 = 0; + while (j < TOTAL_CHAR_ON_PAGE) : (j += 1) { + pages[i][j] = blank; + } + } +} + +fn setVideoBufferBlankPages() void { + _setVideoBuffer(); + for (video_buffer) |*b| { + b.* = blank; + } +} + +fn setVideoBufferIncrementingBlankPages() void { + _setVideoBuffer(); + var i: u16 = 0; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + video_buffer[i] = i; + } +} + +fn setPagesIncrementing() void { + var i: u16 = 0; + while (i < TOTAL_NUM_PAGES) : (i += 1) { + var j: u16 = 0; + while (j < TOTAL_CHAR_ON_PAGE) : (j += 1) { + pages[i][j] = i * TOTAL_CHAR_ON_PAGE + j; + } + } +} + +fn defaultVariablesTesting(p_i: u8, r: u8, c: u8) void { + expectEqual(test_colour, colour); + expectEqual(vga.entry(0, test_colour), blank); + expectEqual(p_i, page_index); + expectEqual(r, row); + expectEqual(c, column); +} + +fn incrementingPagesTesting() void { + var i: u16 = 0; + while (i < TOTAL_NUM_PAGES) : (i += 1) { + var j: u16 = 0; + while (j < TOTAL_CHAR_ON_PAGE) : (j += 1) { + expectEqual(i * TOTAL_CHAR_ON_PAGE + j, pages[i][j]); + } + } +} + +fn blankPagesTesting() void { + var i: u16 = 0; + while (i < TOTAL_NUM_PAGES) : (i += 1) { + var j: u16 = 0; + while (j < TOTAL_CHAR_ON_PAGE) : (j += 1) { + expectEqual(blank, pages[i][j]); + } + } +} + +fn incrementingVideoBufferTesting() void { + var i: u16 = 0; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + expectEqual(i, video_buffer[i]); + } +} + +fn defaultVideoBufferTesting() void { + for (video_buffer) |b| { + expectEqual(vga.entry(0, test_colour), b); + } +} + +fn defaultAllTesting(p_i: u8, r: u8, c: u8) void { + defaultVariablesTesting(p_i, r, c); + blankPagesTesting(); + defaultVideoBufferTesting(); +} + +test "updateCursor" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + defaultAllTesting(0, 0, 0); + + // + // Call function + // + + updateCursor(); + + // + // Post test + // + + defaultAllTesting(0, 0, 0); + + // + // Tear down + // + + resetGlobals(); +} + +test "getCursor all" { + warn(" Waiting for mocking "); +} + +test "displayPageNumber column and row is reset" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + column = 5; + row = 6; + defaultAllTesting(0, 6, 5); + + // + // Call function + // + + displayPageNumber(); + + // + // Post test + // + + defaultVariablesTesting(0, 6, 5); + + const text: [11]u8 = "Page 0 of 4"; + + // Test both video and pages for page number 0 + for (video_buffer) |b, i| { + if (i < START_OF_DISPLAYABLE_REGION - 11) { + expectEqual(blank, b); + } else if (i < START_OF_DISPLAYABLE_REGION) { + expectEqual(vga.entry(text[i + 11 - START_OF_DISPLAYABLE_REGION], colour), b); + } else { + expectEqual(blank, b); + } + } + + // + // Tear down + // + + resetGlobals(); +} + +test "putEntryAt out of bounds" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + defaultAllTesting(0, 0, 0); + + // + // Call function + // + + expectError(PrintError.OutOfBounds, putEntryAt('A', 100, 100)); + + // + // Post test + // + + defaultAllTesting(0, 0, 0); + + // + // Tear down (resets globals) + // + + resetGlobals(); +} + +test "putEntryAt not in displayable region" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + defaultAllTesting(0, 0, 0); + + // + // Call function + // + + const x: u8 = 0; + const y: u8 = 0; + const char: u8 = 'A'; + putEntryAt(char, x, y) catch unreachable; + + // + // Post test + // + + defaultVariablesTesting(0, 0, 0); + blankPagesTesting(); + + for (video_buffer) |b, i| { + if (i == y * vga.WIDTH + x) { + expectEqual(vga.entry(char, test_colour), b); + } else { + expectEqual(vga.entry(0, test_colour), b); + } + } + + // + // Tear down (resets globals) + // + + resetGlobals(); +} + +test "putEntryAt in displayable region page_index is 0" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + defaultAllTesting(0, 0, 0); + + // + // Call function + // + + const x: u16 = 0; + const y: u16 = ROW_MIN; + const char: u8 = 'A'; + putEntryAt(char, x, y) catch unreachable; + + // + // Post test + // + + defaultVariablesTesting(0, 0, 0); + for (pages) |page, i| { + for (page) |c, j| { + if ((i == page_index) and (j == (y * vga.WIDTH + x) - START_OF_DISPLAYABLE_REGION)) { + expectEqual(vga.entry(char, test_colour), c); + } else { + expectEqual(blank, c); + } + } + } + + for (video_buffer) |b, i| { + if (i == y * vga.WIDTH + x) { + expectEqual(vga.entry(char, test_colour), b); + } else { + expectEqual(vga.entry(0, test_colour), b); + } + } + + // + // Tear down (resets globals) + // + + resetGlobals(); +} + +test "putEntryAt in displayable region page_index is not 0" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // Fill the 1'nd page (index 1) will all 1's + const ones: u16 = vga.entry('1', test_colour); + for (pages) |*page, i| { + for (page) |*char| { + if (i == 0) { + char.* = ones; + } else { + char.* = 0; + } + } + } + + page_index = 1; + + // + // Pre testing + // + + defaultVariablesTesting(1, 0, 0); + defaultVideoBufferTesting(); + + for (pages) |page, i| { + for (page) |char| { + if (i == 0) { + expectEqual(ones, char); + } else { + expectEqual(u16(0), char); + } + } + } + + // + // Call function + // + + const x: u16 = 0; + const y: u16 = ROW_MIN; + const char: u8 = 'A'; + putEntryAt(char, x, y) catch unreachable; + + // + // Post test + // + + defaultVariablesTesting(0, 0, 0); + + const text: []const u8 = "Page 0 of 4"; + + for (pages) |page, i| { + for (page) |c, j| { + if (i == 0 and j == 0) { + expectEqual(vga.entry(char, test_colour), c); + } else if (i == 0) { + expectEqual(ones, c); + } else { + expectEqual(u16(0), c); + } + } + } + + // The top 7 rows won't be copied + for (video_buffer) |b, i| { + if (i < START_OF_DISPLAYABLE_REGION - 11) { + expectEqual(blank, b); + } else if (i < START_OF_DISPLAYABLE_REGION) { + expectEqual(vga.entry(text[i + 11 - START_OF_DISPLAYABLE_REGION], colour), b); + } else if (i == y * vga.WIDTH + x) { + expectEqual(vga.entry(char, test_colour), b); + } else { + expectEqual(ones, b); + } + } + + // + // Tear down + // + + resetGlobals(); +} + +test "pagesMoveRowsUp out of bounds" { + // + // Set up + // + + setVideoBufferBlankPages(); + setPagesIncrementing(); + + // + // Pre testing + // + + defaultVariablesTesting(0, 0, 0); + defaultVideoBufferTesting(); + incrementingPagesTesting(); + + // + // Call function + // + + const rows_to_move: u16 = ROW_TOTAL + 1; + expectError(PrintError.OutOfBounds, pagesMoveRowsUp(rows_to_move)); + + // + // Post test + // + + defaultVariablesTesting(0, 0, 0); + defaultVideoBufferTesting(); + incrementingPagesTesting(); + + // + // Tear down + // + + resetGlobals(); +} + +test "pagesMoveRowsUp 0 rows" { + // + // Set up + // + + setVideoBufferBlankPages(); + setPagesIncrementing(); + + // + // Pre testing + // + + defaultVariablesTesting(0, 0, 0); + defaultVideoBufferTesting(); + incrementingPagesTesting(); + + // + // Call function + // + + const rows_to_move: u16 = 0; + pagesMoveRowsUp(rows_to_move) catch unreachable; + + // + // Post test + // + + defaultVariablesTesting(0, 0, 0); + defaultVideoBufferTesting(); + incrementingPagesTesting(); + + // + // Tear down + // + + resetGlobals(); +} + +test "pagesMoveRowsUp 1 rows" { + // + // Set up + // + + setVideoBufferBlankPages(); + setPagesIncrementing(); + + // + // Pre testing + // + + defaultVariablesTesting(0, 0, 0); + defaultVideoBufferTesting(); + incrementingPagesTesting(); + + // + // Call function + // + + const rows_to_move: u16 = 1; + pagesMoveRowsUp(rows_to_move) catch unreachable; + + // + // Post test + // + + defaultVariablesTesting(0, 0, 0); + defaultVideoBufferTesting(); + + var i: u16 = 0; + const to_add: u16 = rows_to_move * vga.WIDTH; + while (i < TOTAL_NUM_PAGES) : (i += 1) { + var j: u16 = 0; + while (j < TOTAL_CHAR_ON_PAGE) : (j += 1) { + if (j >= TOTAL_CHAR_ON_PAGE - to_add) { + if (i == 0) { + // The last rows will be blanks + expectEqual(blank, pages[i][j]); + } else { + expectEqual((i - 1) * TOTAL_CHAR_ON_PAGE + (j + to_add - TOTAL_CHAR_ON_PAGE), pages[i][j]); + } + } else { + // All rows moved up one, so add vga.WIDTH + expectEqual(i * TOTAL_CHAR_ON_PAGE + j + to_add, pages[i][j]); + } + } + } + + // + // Tear down + // + + resetGlobals(); +} + +test "pagesMoveRowsUp ROW_TOTAL - 1 rows" { + // + // Set up + // + + setVideoBufferBlankPages(); + setPagesIncrementing(); + + // + // Pre testing + // + + defaultVariablesTesting(0, 0, 0); + defaultVideoBufferTesting(); + incrementingPagesTesting(); + + // + // Call function + // + + const rows_to_move: u16 = ROW_TOTAL - 1; + pagesMoveRowsUp(rows_to_move) catch unreachable; + + // + // Post test + // + + defaultVariablesTesting(0, 0, 0); + defaultVideoBufferTesting(); + + var i: u16 = 0; + const to_add: u16 = rows_to_move * vga.WIDTH; + while (i < TOTAL_NUM_PAGES) : (i += 1) { + var j: u16 = 0; + while (j < TOTAL_CHAR_ON_PAGE) : (j += 1) { + if (j >= TOTAL_CHAR_ON_PAGE - to_add) { + if (i == 0) { + // The last rows will be blanks + expectEqual(blank, pages[i][j]); + } else { + expectEqual((i - 1) * TOTAL_CHAR_ON_PAGE + (j + to_add - TOTAL_CHAR_ON_PAGE), pages[i][j]); + } + } else { + // All rows moved up one, so add vga.WIDTH + expectEqual(i * TOTAL_CHAR_ON_PAGE + j + to_add, pages[i][j]); + } + } + } + + // + // Tear down + // + + resetGlobals(); +} + +test "pagesMoveRowsUp ROW_TOTAL rows" { + // + // Set up + // + + setVideoBufferBlankPages(); + setPagesIncrementing(); + + // + // Pre testing + // + + defaultVariablesTesting(0, 0, 0); + defaultVideoBufferTesting(); + incrementingPagesTesting(); + + // + // Call function + // + + const rows_to_move: u16 = ROW_TOTAL; + pagesMoveRowsUp(rows_to_move) catch unreachable; + + // + // Post test + // + + defaultVariablesTesting(0, 0, 0); + defaultVideoBufferTesting(); + + var i: u16 = 0; + while (i < TOTAL_NUM_PAGES) : (i += 1) { + var j: u16 = 0; + while (j < TOTAL_CHAR_ON_PAGE) : (j += 1) { + if (i == 0) { + // The last rows will be blanks + expectEqual(blank, pages[i][j]); + } else { + expectEqual((i - 1) * TOTAL_CHAR_ON_PAGE + j, pages[i][j]); + } + } + } + + // + // Tear down + // + + resetGlobals(); +} + +test "scroll row is less then max height" { + // + // Set up + // + + setVideoBufferBlankPages(); + setPagesIncrementing(); + + // + // Pre testing + // + + defaultVariablesTesting(0, 0, 0); + defaultVideoBufferTesting(); + incrementingPagesTesting(); + + // + // Call function + // + + scroll(); + + // + // Post test + // + + defaultVariablesTesting(0, 0, 0); + defaultVideoBufferTesting(); + incrementingPagesTesting(); + + // + // Tear down + // + + resetGlobals(); +} + +test "scroll row is equal to height" { + // + // Set up + // + + setVideoBufferIncrementingBlankPages(); + setPagesIncrementing(); + + const row_test: u16 = vga.HEIGHT; + row = row_test; + + // + // Pre testing + // + + defaultVariablesTesting(0, row_test, 0); + incrementingPagesTesting(); + incrementingVideoBufferTesting(); + + // + // Call function + // + + // Rows move up one + scroll(); + + // + // Post test + // + + defaultVariablesTesting(0, vga.HEIGHT - 1, 0); + + var i: u16 = 0; + const to_add: u16 = (row_test - vga.HEIGHT + 1) * vga.WIDTH; + while (i < TOTAL_NUM_PAGES) : (i += 1) { + var j: u16 = 0; + while (j < TOTAL_CHAR_ON_PAGE) : (j += 1) { + if (j >= TOTAL_CHAR_ON_PAGE - to_add) { + if (i == 0) { + // The last rows will be blanks + expectEqual(blank, pages[i][j]); + } else { + expectEqual((i - 1) * TOTAL_CHAR_ON_PAGE + (j + to_add - TOTAL_CHAR_ON_PAGE), pages[i][j]); + } + } else { + // All rows moved up one, so add vga.WIDTH + expectEqual(i * TOTAL_CHAR_ON_PAGE + j + to_add, pages[i][j]); + } + } + } + + var k: u16 = 0; + while (k < VIDEO_BUFFER_SIZE) : (k += 1) { + if (k < START_OF_DISPLAYABLE_REGION) { + expectEqual(k, video_buffer[k]); + } else if (k >= VIDEO_BUFFER_SIZE - to_add) { + expectEqual(blank, video_buffer[k]); + } else { + expectEqual(k + to_add, video_buffer[k]); + } + } + + // + // Tear down + // + + resetGlobals(); +} + +test "scroll row is more than height" { + // + // Set up + // + + setVideoBufferIncrementingBlankPages(); + setPagesIncrementing(); + + const row_test: u16 = vga.HEIGHT + 5; + row = row_test; + + // + // Pre testing + // + + defaultVariablesTesting(0, row_test, 0); + incrementingPagesTesting(); + incrementingVideoBufferTesting(); + + // + // Call function + // + + // Rows move up 5 + scroll(); + + // + // Post test + // + + defaultVariablesTesting(0, vga.HEIGHT - 1, 0); + + var i: u16 = 0; + const to_add: u16 = (row_test - vga.HEIGHT + 1) * vga.WIDTH; + while (i < TOTAL_NUM_PAGES) : (i += 1) { + var j: u16 = 0; + while (j < TOTAL_CHAR_ON_PAGE) : (j += 1) { + if (j >= TOTAL_CHAR_ON_PAGE - to_add) { + if (i == 0) { + // The last rows will be blanks + expectEqual(blank, pages[i][j]); + } else { + expectEqual((i - 1) * TOTAL_CHAR_ON_PAGE + (j + to_add - TOTAL_CHAR_ON_PAGE), pages[i][j]); + } + } else { + // All rows moved up one, so add vga.WIDTH + expectEqual(i * TOTAL_CHAR_ON_PAGE + j + to_add, pages[i][j]); + } + } + } + + var k: u16 = 0; + while (k < VIDEO_BUFFER_SIZE) : (k += 1) { + if (k < START_OF_DISPLAYABLE_REGION) { + expectEqual(k, video_buffer[k]); + } else if (k >= VIDEO_BUFFER_SIZE - to_add) { + expectEqual(blank, video_buffer[k]); + } else { + expectEqual(k + to_add, video_buffer[k]); + } + } + + // + // Tear down + // + + resetGlobals(); +} + +test "putChar new line within screen" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + column = 5; + row = 5; + defaultAllTesting(0, 5, 5); + + // + // Call function + // + + putChar('\n') catch unreachable; + + // + // Post test + // + + defaultAllTesting(0, 6, 0); + + // + // Tear down + // + + resetGlobals(); +} + +test "putChar new line outside screen" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + column = 5; + row = vga.HEIGHT - 1; + defaultAllTesting(0, vga.HEIGHT - 1, 5); + + // + // Call function + // + + putChar('\n') catch unreachable; + + // + // Post test + // + + defaultAllTesting(0, vga.HEIGHT - 1, 0); + + // + // Tear down + // + + resetGlobals(); +} + +test "putChar tab within line" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + column = 5; + row = 6; + defaultAllTesting(0, 6, 5); + + // + // Call function + // + + putChar('\t') catch unreachable; + + // + // Post test + // + + defaultAllTesting(0, 6, 9); + + // + // Tear down + // + + resetGlobals(); +} + +test "putChar tab end of line" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + column = vga.WIDTH - 1; + row = 6; + defaultAllTesting(0, 6, vga.WIDTH - 1); + + // + // Call function + // + + putChar('\t') catch unreachable; + + // + // Post test + // + + defaultAllTesting(0, 7, 3); + + // + // Tear down + // + + resetGlobals(); +} + +test "putChar tab end of screen" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + column = vga.WIDTH - 1; + row = vga.HEIGHT - 1; + defaultAllTesting(0, vga.HEIGHT - 1, vga.WIDTH - 1); + + // + // Call function + // + + putChar('\t') catch unreachable; + + // + // Post test + // + + defaultAllTesting(0, vga.HEIGHT - 1, 3); + + // + // Tear down + // + + resetGlobals(); +} + +test "putChar line feed" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + column = vga.WIDTH - 1; + row = vga.HEIGHT - 1; + defaultAllTesting(0, vga.HEIGHT - 1, vga.WIDTH - 1); + + // + // Call function + // + + putChar('\r') catch unreachable; + + // + // Post test + // + + defaultAllTesting(0, vga.HEIGHT - 1, 0); + + // + // Tear down + // + + resetGlobals(); +} + +test "putChar back char top left of screen" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + defaultAllTesting(0, 0, 0); + + // + // Call function + // + + putChar('\x08') catch unreachable; + + // + // Post test + // + + defaultAllTesting(0, 0, 0); + + // + // Tear down + // + + resetGlobals(); +} + +test "putChar back char top row" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + column = 8; + defaultAllTesting(0, 0, 8); + + // + // Call function + // + + putChar('\x08') catch unreachable; + + // + // Post test + // + + defaultAllTesting(0, 0, 7); + + // + // Tear down + // + + resetGlobals(); +} + +test "putChar back char beginning of row" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + row = 1; + defaultAllTesting(0, 1, 0); + + // + // Call function + // + + putChar('\x08') catch unreachable; + + // + // Post test + // + + defaultAllTesting(0, 0, vga.WIDTH - 1); + + // + // Tear down + // + + resetGlobals(); +} + +test "putChar any char in row" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + defaultAllTesting(0, 0, 0); + + // + // Call function + // + + putChar('A') catch unreachable; + + // + // Post test + // + + defaultVariablesTesting(0, 0, 1); + blankPagesTesting(); + + var k: u16 = 0; + while (k < VIDEO_BUFFER_SIZE) : (k += 1) { + if (k == 0) { + expectEqual(vga.entry('A', colour), video_buffer[k]); + } else { + expectEqual(blank, video_buffer[k]); + } + } + + // + // Tear down + // + + resetGlobals(); +} + +test "putChar any char end of row" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + column = vga.WIDTH - 1; + defaultAllTesting(0, 0, vga.WIDTH - 1); + + // + // Call function + // + + putChar('A') catch unreachable; + + // + // Post test + // + + defaultVariablesTesting(0, 1, 0); + blankPagesTesting(); + + var k: u16 = 0; + while (k < VIDEO_BUFFER_SIZE) : (k += 1) { + if (k == vga.WIDTH - 1) { + expectEqual(vga.entry('A', colour), video_buffer[k]); + } else { + expectEqual(blank, video_buffer[k]); + } + } + + // + // Tear down + // + + resetGlobals(); +} + +test "putChar any char end of screen" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + row = vga.HEIGHT - 1; + column = vga.WIDTH - 1; + defaultAllTesting(0, vga.HEIGHT - 1, vga.WIDTH - 1); + + // + // Call function + // + + putChar('A') catch unreachable; + + // + // Post test + // + + defaultVariablesTesting(0, vga.HEIGHT - 1, 0); + for (pages) |page, i| { + for (page) |c, j| { + if ((i == 0) and (j == TOTAL_CHAR_ON_PAGE - vga.WIDTH - 1)) { + expectEqual(vga.entry('A', colour), c); + } else { + expectEqual(blank, c); + } + } + } + + + var k: u16 = 0; + while (k < VIDEO_BUFFER_SIZE) : (k += 1) { + if (k == VIDEO_BUFFER_SIZE - vga.WIDTH - 1) { + expectEqual(vga.entry('A', colour), video_buffer[k]); + } else { + expectEqual(blank, video_buffer[k]); + } + } + + // + // Tear down + // + + resetGlobals(); +} + +test "printLogo all" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + column = 0; + row = ROW_MIN; + + defaultAllTesting(0, ROW_MIN, 0); + + // + // Call function + // + + printLogo(); + + // + // Post test + // + + defaultVariablesTesting(0, ROW_MIN, 0); + blankPagesTesting(); + + var k: u16 = 0; + while (k < VIDEO_BUFFER_SIZE) : (k += 1) { + if (k < START_OF_DISPLAYABLE_REGION) { + // This is where the logo will be, but is a complex string so no testing + // Just take my word it works :P + } else { + expectEqual(blank, video_buffer[k]); + } + } + + // + // Tear down + // + + resetGlobals(); +} + +test "pageUp top page" { + // + // Set up + // + + setVideoBufferBlankPages(); + setPagesIncrementing(); + + // + // Pre testing + // + + page_index = TOTAL_NUM_PAGES - 1; + + defaultVariablesTesting(TOTAL_NUM_PAGES - 1, 0, 0); + incrementingPagesTesting(); + defaultVideoBufferTesting(); + + // + // Call function + // + + pageUp(); + + // + // Post test + // + + defaultVariablesTesting(TOTAL_NUM_PAGES - 1, 0, 0); + incrementingPagesTesting(); + defaultVideoBufferTesting(); + + // + // Tear down + // + + resetGlobals(); +} + +test "pageUp bottom page" { + // + // Set up + // + + setVideoBufferBlankPages(); + setPagesIncrementing(); + + // + // Pre testing + // + + defaultVariablesTesting(0, 0, 0); + incrementingPagesTesting(); + defaultVideoBufferTesting(); + + // + // Call function + // + + pageUp(); + + // + // Post test + // + + defaultVariablesTesting(1, 0, 0); + incrementingPagesTesting(); + + const text: []const u8 = "Page 1 of 4"; + + for (video_buffer) |b, i| { + // Ignore the ROW_MIN row as this is where the page number is printed and is already + // tested, page number is printed 11 from the end + if (i < START_OF_DISPLAYABLE_REGION - 11) { + expectEqual(blank, b); + } else if (i < START_OF_DISPLAYABLE_REGION) { + expectEqual(vga.entry(text[i + 11 - START_OF_DISPLAYABLE_REGION], colour), b); + } else { + expectEqual(i - START_OF_DISPLAYABLE_REGION + TOTAL_CHAR_ON_PAGE, b); + } + } + + // + // Tear down + // + + resetGlobals(); +} + +test "pageDown bottom page" { + // + // Set up + // + + setVideoBufferBlankPages(); + setPagesIncrementing(); + + // + // Pre testing + // + + defaultVariablesTesting(0, 0, 0); + incrementingPagesTesting(); + defaultVideoBufferTesting(); + + // + // Call function + // + + pageDown(); + + // + // Post test + // + + defaultVariablesTesting(0, 0, 0); + incrementingPagesTesting(); + defaultVideoBufferTesting(); + + // + // Tear down + // + + resetGlobals(); +} + +test "pageDown top page" { + // + // Set up + // + + setVideoBufferBlankPages(); + setPagesIncrementing(); + + // + // Pre testing + // + + page_index = TOTAL_NUM_PAGES - 1; + + defaultVariablesTesting(TOTAL_NUM_PAGES - 1, 0, 0); + incrementingPagesTesting(); + defaultVideoBufferTesting(); + + // + // Call function + // + + pageDown(); + + // + // Post test + // + + defaultVariablesTesting(TOTAL_NUM_PAGES - 2, 0, 0); + incrementingPagesTesting(); + + const text: []const u8 = "Page 3 of 4"; + + for (video_buffer) |b, i| { + // Ignore the ROW_MIN row as this is where the page number is printed and is already + // tested, page number is printed 11 from the end + if (i < START_OF_DISPLAYABLE_REGION - 11) { + expectEqual(blank, b); + } else if (i < START_OF_DISPLAYABLE_REGION) { + expectEqual(vga.entry(text[i + 11 - START_OF_DISPLAYABLE_REGION], colour), b); + } else { + expectEqual((i - START_OF_DISPLAYABLE_REGION) + (TOTAL_CHAR_ON_PAGE * page_index), b); + } + } + + // + // Tear down + // + + resetGlobals(); +} + +test "clearScreen all" { + // + // Set up + // + + setVideoBufferIncrementingBlankPages(); + setPagesIncrementing(); + + // + // Pre testing + // + + defaultVariablesTesting(0, 0, 0); + incrementingVideoBufferTesting(); + incrementingPagesTesting(); + + // + // Call function + // + + clearScreen(); + + // + // Post test + // + + defaultVariablesTesting(0, ROW_MIN, 0); + var k: u16 = 0; + while (k < VIDEO_BUFFER_SIZE) : (k += 1) { + if (k < START_OF_DISPLAYABLE_REGION) { + expectEqual(k, video_buffer[k]); + } else { + expectEqual(blank, video_buffer[k]); + } + } + + var i: u16 = 0; + while (i < TOTAL_NUM_PAGES) : (i += 1) { + var j: u16 = 0; + while (j < TOTAL_CHAR_ON_PAGE) : (j += 1) { + if (i == 0) { + // The last rows will be blanks + expectEqual(blank, pages[i][j]); + } else { + expectEqual((i - 1) * TOTAL_CHAR_ON_PAGE + j, pages[i][j]); + } + } + } + + // + // Tear down + // + + resetGlobals(); +} + +test "moveCursorLeft top left of screen" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + defaultAllTesting(0, 0, 0); + + // + // Call function + // + + moveCursorLeft(); + + // + // Post test + // + + defaultAllTesting(0, 0, 0); + + // + // Tear down + // + + resetGlobals(); +} + +test "moveCursorLeft top screen" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + column = 5; + defaultAllTesting(0, 0, 5); + + // + // Call function + // + + moveCursorLeft(); + + // + // Post test + // + + defaultAllTesting(0, 0, 4); + + // + // Tear down + // + + resetGlobals(); +} + +test "moveCursorLeft start of row" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + row = 5; + defaultAllTesting(0, 5, 0); + + // + // Call function + // + + moveCursorLeft(); + + // + // Post test + // + + defaultAllTesting(0, 4, vga.WIDTH - 1); + + // + // Tear down + // + + resetGlobals(); +} + +test "moveCursorRight bottom right of screen" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + row = vga.HEIGHT - 1; + column = vga.WIDTH - 1; + defaultAllTesting(0, vga.HEIGHT - 1, vga.WIDTH - 1); + + // + // Call function + // + + moveCursorRight(); + + // + // Post test + // + + defaultAllTesting(0, vga.HEIGHT - 1, vga.WIDTH - 1); + + // + // Tear down + // + + resetGlobals(); +} + +test "moveCursorRight top screen" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + column = 5; + defaultAllTesting(0, 0, 5); + + // + // Call function + // + + moveCursorRight(); + + // + // Post test + // + + defaultAllTesting(0, 0, 6); + + // + // Tear down + // + + resetGlobals(); +} + +test "moveCursorRight end of row" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + + row = 5; + column = vga.WIDTH - 1; + defaultAllTesting(0, 5, vga.WIDTH - 1); + + // + // Call function + // + + moveCursorRight(); + + // + // Post test + // + + defaultAllTesting(0, 6, 0); + + // + // Tear down + // + + resetGlobals(); +} + +test "setColour all" { + // + // Set up + // + + // + // Pre testing + // + + // + // Call function + // + + const new_colour: u8 = vga.entryColour(vga.COLOUR_WHITE, vga.COLOUR_WHITE); + setColour(new_colour); + + // + // Post test + // + + expectEqual(new_colour, colour); + expectEqual(vga.entry(0, new_colour), blank); + + // + // Tear down + // + resetGlobals(); +} + +test "writeString all" { + // + // Set up + // + + setVideoBufferBlankPages(); + + // + // Pre testing + // + row = ROW_MIN; + defaultAllTesting(0, ROW_MIN, 0); + + // + // Call function + // + + writeString("ABC") catch unreachable; + + // + // Post test + // + + defaultVariablesTesting(0, ROW_MIN, 3); + for (pages) |page, i| { + for (page) |c, j| { + if ((i == 0) and (j == 0)) { + expectEqual(vga.entry('A', colour), c); + } else if ((i == 0) and (j == 1)) { + expectEqual(vga.entry('B', colour), c); + } else if ((i == 0) and (j == 2)) { + expectEqual(vga.entry('C', colour), c); + } else { + expectEqual(blank, c); + } + } + } + + + var k: u16 = 0; + while (k < VIDEO_BUFFER_SIZE) : (k += 1) { + if (k == START_OF_DISPLAYABLE_REGION) { + expectEqual(vga.entry('A', colour), video_buffer[k]); + } else if (k == START_OF_DISPLAYABLE_REGION + 1) { + expectEqual(vga.entry('B', colour), video_buffer[k]); + } else if (k == START_OF_DISPLAYABLE_REGION + 2) { + expectEqual(vga.entry('C', colour), video_buffer[k]); + } else { + expectEqual(blank, video_buffer[k]); + } + } + + // + // Tear down + // + + resetGlobals(); +} diff --git a/src/kernel/vga.zig b/src/kernel/vga.zig new file mode 100644 index 0000000..e778655 --- /dev/null +++ b/src/kernel/vga.zig @@ -0,0 +1,413 @@ +// Zig version: 0.4.0 + +const builtin = @import("builtin"); +const arch = if (builtin.is_test) @import("../../test/kernel/arch_mock.zig") + else @import("arch.zig"); + +const expectEqual = @import("std").testing.expectEqual; +const warn = @import("std").debug.warn; + +/// The port address for the VGA register selection. +const PORT_ADDRESS: u16 = 0x03D4; + +/// The port address for the VGA data. +const PORT_DATA: u16 = 0x03D5; + +// The indexes that is passed to the address port to select the register for the data to be +// read or written to. + +const REG_HORIZONTAL_TOTAL: u8 = 0x00; +const REG_HORIZONTAL_DISPLAY_ENABLE_END: u8 = 0x01; +const REG_START_HORIZONTAL_BLINKING: u8 = 0x02; +const REG_END_HORIZONTAL_BLINKING: u8 = 0x03; +const REG_START_HORIZONTAL_RETRACE_PULSE: u8 = 0x04; +const REG_END_HORIZONTAL_RETRACE_PULSE: u8 = 0x05; +const REG_VERTICAL_TOTAL: u8 = 0x06; +const REG_OVERFLOW: u8 = 0x07; +const REG_PRESET_ROW_SCAN: u8 = 0x08; +const REG_MAXIMUM_SCAN_LINE: u8 = 0x09; + +/// The command for setting the start of the cursor scan line. +const REG_CURSOR_START: u8 = 0x0A; + +/// The command for setting the end of the cursor scan line. +const REG_CURSOR_END: u8 = 0x0B; +const REG_START_ADDRESS_HIGH: u8 = 0x0C; +const REG_START_ADDRESS_LOW: u8 = 0x0D; + +/// The command for setting the upper byte of the cursor's linear location. +const REG_CURSOR_LOCATION_HIGH: u8 = 0x0E; + +/// The command for setting the lower byte of the cursor's linear location. +const REG_CURSOR_LOCATION_LOW: u8 = 0x0F; +const REG_VERTICAL_RETRACE_START: u8 = 0x10; +const REG_VERTICAL_RETRACE_END: u8 = 0x11; +const REG_VERTICAL_DISPLAY_ENABLE_END: u8 = 0x12; +const REG_OFFSET: u8 = 0x13; +const REG_UNDERLINE_LOCATION: u8 = 0x14; +const REG_START_VERTICAL_BLINKING: u8 = 0x15; +const REG_END_VERTICAL_BLINKING: u8 = 0x16; +const REG_CRT_MODE_CONTROL: u8 = 0x17; +const REG_LINE_COMPARE: u8 = 0x18; + + +///The start of the cursor scan line, the very beginning. +const CURSOR_SCANLINE_START: u8 = 0x0; + +///The scan line for use in the underline cursor shape. +const CURSOR_SCANLINE_MIDDLE: u8 = 0xE; + +///The end of the cursor scan line, the very end. +const CURSOR_SCANLINE_END: u8 = 0xF; + +/// If set, disables the cursor. +const CURSOR_DISABLE: u8 = 0x20; + +pub const WIDTH: u16 = 80; +pub const HEIGHT: u16 = 25; + +// The set of colours that VGA supports and can display for the foreground and background. +pub const COLOUR_BLACK: u4 = 0x00; +pub const COLOUR_BLUE: u4 = 0x01; +pub const COLOUR_GREEN: u4 = 0x02; +pub const COLOUR_CYAN: u4 = 0x03; +pub const COLOUR_RED: u4 = 0x04; +pub const COLOUR_MAGENTA: u4 = 0x05; +pub const COLOUR_BROWN: u4 = 0x06; +pub const COLOUR_LIGHT_GREY: u4 = 0x07; +pub const COLOUR_DARK_GREY: u4 = 0x08; +pub const COLOUR_LIGHT_BLUE: u4 = 0x09; +pub const COLOUR_LIGHT_GREEN: u4 = 0x0A; +pub const COLOUR_LIGHT_CYAN: u4 = 0x0B; +pub const COLOUR_LIGHT_RED: u4 = 0x0C; +pub const COLOUR_LIGHT_MAGENTA: u4 = 0x0D; +pub const COLOUR_LIGHT_BROWN: u4 = 0x0E; +pub const COLOUR_WHITE: u4 = 0x0F; + +/// The set of shapes that can be displayed. +pub const CursorShape = enum(u1) { + /// The cursor has the underline shape. + UNDERLINE, + + /// The cursor has the block shape. + BLOCK, +}; + +/// The cursor scan line start so to know whether is in block or underline mode. +var cursor_scanline_start: u8 = undefined; + +/// The cursor scan line end so to know whether is in block or underline mode. +var cursor_scanline_end: u8 = undefined; + +/// +/// Takes two 4 bit values that represent the foreground and background colour of the text and +/// returns a 8 bit value that gives both to be displayed. +/// +/// Arguments: +/// IN fg: u4 - The foreground colour. +/// IN bg: u4 - The background colour. +/// +/// Return: +/// Both combined into 1 byte for the colour to be displayed. +/// +pub fn entryColour(fg: u4, bg: u4) u8 { + return u8(fg) | u8(bg) << 4; +} + +/// +/// Create the 2 bytes entry that the VGA used to display a character with a foreground and +/// background colour. +/// +/// Arguments: +/// IN uc: u8 - The character. +/// IN colour: u8 - The foreground and background colour. +/// +/// Return: +/// The VGA entry. +/// +pub fn entry(uc: u8, colour: u8) u16 { + return u16(uc) | u16(colour) << 8; +} + +/// +/// Update the hardware on screen cursor. +/// +/// Arguments: +/// IN x: u16 - The horizontal position of the cursor. +/// IN y: u16 - The vertical position of the cursor. +/// +/// Return: +/// The VGA entry. +/// +pub fn updateCursor(x: u16, y: u16) void { + var pos: u16 = undefined; + var pos_upper: u16 = undefined; + var pos_lower: u16 = undefined; + + // Make sure new cursor position is within the screen + if (x < HEIGHT and y < WIDTH) { + pos = y * WIDTH + x; + } else { + // If not within the screen, then just put the cursor at the very end + pos = (HEIGHT - 1) * WIDTH + (WIDTH - 1); + } + + pos_upper = (pos >> 8) & 0x00FF; + pos_lower = pos & 0x00FF; + + // Set the cursor position + arch.outb(PORT_ADDRESS, REG_CURSOR_LOCATION_LOW); + arch.outb(PORT_DATA, @truncate(u8, pos_lower)); + + arch.outb(PORT_ADDRESS, REG_CURSOR_LOCATION_HIGH); + arch.outb(PORT_DATA, @truncate(u8, pos_upper)); +} + +/// +/// Get the hardware cursor position. +/// +/// Return: +/// The linear cursor position. +/// +pub fn getCursor() u16 { + var cursor: u16 = 0; + + arch.outb(PORT_ADDRESS, REG_CURSOR_LOCATION_LOW); + cursor |= u16(arch.inb(PORT_DATA)); + + arch.outb(PORT_ADDRESS, REG_CURSOR_LOCATION_HIGH); + cursor |= u16(arch.inb(PORT_DATA)) << 8; + + return cursor; +} + +/// +/// Enables the blinking cursor to that is is visible. +/// +pub fn enableCursor() void { + arch.outb(PORT_ADDRESS, REG_CURSOR_START); + arch.outb(PORT_DATA, cursor_scanline_start); + + arch.outb(PORT_ADDRESS, REG_CURSOR_END); + arch.outb(PORT_DATA, cursor_scanline_end); +} + +/// +/// Disables the blinking cursor to that is is visible. +/// +pub fn disableCursor() void { + arch.outb(PORT_ADDRESS, REG_CURSOR_START); + arch.outb(PORT_DATA, CURSOR_DISABLE); +} + +/// +/// Set the shape of the cursor. This can be and underline or block shape. +/// +/// Arguments: +/// IN shape: CURSOR_SHAPE - The enum CURSOR_SHAPE that selects which shape to use. +/// +pub fn setCursorShape(shape: CursorShape) void { + switch(shape) { + CursorShape.UNDERLINE => { + arch.outb(PORT_ADDRESS, REG_CURSOR_START); + arch.outb(PORT_DATA, CURSOR_SCANLINE_MIDDLE); + + arch.outb(PORT_ADDRESS, REG_CURSOR_END); + arch.outb(PORT_DATA, CURSOR_SCANLINE_END); + + cursor_scanline_start = CURSOR_SCANLINE_MIDDLE; + cursor_scanline_end = CURSOR_SCANLINE_END; + }, + CursorShape.BLOCK => { + arch.outb(PORT_ADDRESS, REG_CURSOR_START); + arch.outb(PORT_DATA, CURSOR_SCANLINE_START); + + arch.outb(PORT_ADDRESS, REG_CURSOR_END); + arch.outb(PORT_DATA, CURSOR_SCANLINE_END); + + cursor_scanline_start = CURSOR_SCANLINE_START; + cursor_scanline_end = CURSOR_SCANLINE_END; + }, + } +} + +/// +/// Initialise the VGA text mode. This sets the cursor and underline shape. +/// +pub fn init() void { + // Set the maximum scan line to 0x0F + arch.outb(PORT_ADDRESS, REG_MAXIMUM_SCAN_LINE); + arch.outb(PORT_DATA, CURSOR_SCANLINE_END); + + // Set by default the underline cursor + setCursorShape(CursorShape.UNDERLINE); + cursor_scanline_start = CURSOR_SCANLINE_MIDDLE; + cursor_scanline_end = CURSOR_SCANLINE_END; +} + +test "entryColour" { + var fg: u4 = COLOUR_BLACK; + var bg: u4 = COLOUR_BLACK; + var res: u8 = entryColour(fg, bg); + expectEqual(u8(0x00), res); + + fg = COLOUR_LIGHT_GREEN; + bg = COLOUR_BLACK; + res = entryColour(fg, bg); + expectEqual(u8(0x0A), res); + + fg = COLOUR_BLACK; + bg = COLOUR_LIGHT_GREEN; + res = entryColour(fg, bg); + expectEqual(u8(0xA0), res); + + fg = COLOUR_BROWN; + bg = COLOUR_LIGHT_GREEN; + res = entryColour(fg, bg); + expectEqual(u8(0xA6), res); +} + +test "entry" { + var colour: u8 = entryColour(COLOUR_BROWN, COLOUR_LIGHT_GREEN); + expectEqual(u8(0xA6), colour); + + // Character '0' is 0x30 + var video_entry: u16 = entry('0', colour); + expectEqual(u16(0xA630), video_entry); + + video_entry = entry(0x55, colour); + expectEqual(u16(0xA655), video_entry); +} + +fn testOutOfBounds(x: u16, y: u16) bool { + if(x < HEIGHT and y < WIDTH) { + return true; + } + return false; +} + +fn testUpperVal(x: u16, y: u16) u16 { + const pos: u16 = x * WIDTH + y; + const pos_upper: u16 = (pos >> 8) & 0x00FF; + return pos_upper; +} + +fn testLowerVal(x: u16, y: u16) u16 { + const pos: u16 = x * WIDTH + y; + const pos_lower: u16 = pos & 0x00FF; + return pos_lower; +} + +test "updateCursor out of bounds" { + var x: u16 = 0; + var y: u16 = 0; + var res: bool = testOutOfBounds(x, y); + expectEqual(true, res); + + x = HEIGHT - 1; + res = testOutOfBounds(x, y); + expectEqual(true, res); + + y = WIDTH - 1; + res = testOutOfBounds(x, y); + expectEqual(true, res); + + x = HEIGHT; + y = WIDTH; + res = testOutOfBounds(x, y); + expectEqual(false, res); + + x = HEIGHT - 1; + y = WIDTH; + res = testOutOfBounds(x, y); + expectEqual(false, res); + + x = HEIGHT; + y = WIDTH - 1; + res = testOutOfBounds(x, y); + expectEqual(false, res); +} + +test "updateCursor lower values" { + var x: u16 = 0x0000; + var y: u16 = 0x0000; + var res: u16 = testLowerVal(x, y); + var expected: u16 = 0x0000; + expectEqual(expected, res); + + x = 0x0000; + y = 0x000A; + res = testLowerVal(x, y); + expected = 0x000A; + expectEqual(expected, res); + + x = 0x000A; + y = 0x0000; + res = testLowerVal(x, y); + expected = 0x0020; + expectEqual(expected, res); + + x = 0x000A; + y = 0x000A; + res = testLowerVal(x, y); + expected = 0x002A; + expectEqual(expected, res); +} + +test "updateCursor upper values" { + var x: u16 = 0x0000; + var y: u16 = 0x0000; + var res: u16 = testUpperVal(x, y); + var expected: u16 = 0x0000; + expectEqual(expected, res); + + x = 0x0000; + y = 0x000A; + res = testUpperVal(x, y); + expected = 0x0000; + expectEqual(expected, res); + + x = 0x000A; + y = 0x0000; + res = testUpperVal(x, y); + expected = 0x0003; + expectEqual(expected, res); + + x = 0x000A; + y = 0x000A; + res = testUpperVal(x, y); + expected = 0x0003; + expectEqual(expected, res); +} + +test "getCursor all" { + warn(" Waiting for mocking "); + var res = getCursor(); +} + +test "enableCursor all" { + warn(" Waiting for mocking "); + enableCursor(); +} + +test "disableCursor all" { + warn(" Waiting for mocking "); + disableCursor(); +} + +test "setCursorShape all" { + setCursorShape(CursorShape.UNDERLINE); + expectEqual(CURSOR_SCANLINE_MIDDLE, cursor_scanline_start); + expectEqual(CURSOR_SCANLINE_END, cursor_scanline_end); + + setCursorShape(CursorShape.BLOCK); + expectEqual(CURSOR_SCANLINE_START, cursor_scanline_start); + expectEqual(CURSOR_SCANLINE_END, cursor_scanline_end); +} + +test "init all" { + warn(" Waiting for mocking "); + init(); + expectEqual(CURSOR_SCANLINE_MIDDLE, cursor_scanline_start); + expectEqual(CURSOR_SCANLINE_END, cursor_scanline_end); +} \ No newline at end of file diff --git a/test/kernel/arch_mock.zig b/test/kernel/arch_mock.zig new file mode 100644 index 0000000..30dcaa6 --- /dev/null +++ b/test/kernel/arch_mock.zig @@ -0,0 +1,32 @@ +// Zig version: 0.4.0 + +/// +/// Initialise the architecture +/// +pub fn init() void {} + +/// +/// Inline assembly to write to a given port with a byte of data. +/// +/// Arguments: +/// IN port: u16 - The port to write to. +/// IN data: u8 - The byte of data that will be sent. +/// +pub fn outb(port: u16, data: u8) void {} + +/// +/// Inline assembly that reads data from a given port and returns its value. +/// +/// Arguments: +/// IN port: u16 - The port to read data from. +/// +/// Return: +/// The data that the port returns. +/// +pub fn inb(port: u16) u8 {return 0;} + +/// +/// A simple way of waiting for I/O event to happen by doing an I/O event to flush the I/O +/// event being waited. +/// +pub fn ioWait() void {}