From e2533a2264435f8f206efb11a3d36215072af95d Mon Sep 17 00:00:00 2001 From: Sam Tebbs Date: Fri, 12 Jun 2020 12:05:50 +0100 Subject: [PATCH] Move tty to arch --- src/kernel/arch/x86/arch.zig | 31 +- src/kernel/arch/x86/tty.zig | 2106 +++++++++++++++++++++++++++ src/kernel/{ => arch/x86}/vga.zig | 7 +- src/kernel/kmain.zig | 15 +- src/kernel/tty.zig | 2255 +---------------------------- test/kernel/arch/x86/rt-test.py | 5 + test/mock/kernel/arch_mock.zig | 11 + test/rt-test.py | 10 +- 8 files changed, 2231 insertions(+), 2209 deletions(-) create mode 100644 src/kernel/arch/x86/tty.zig rename src/kernel/{ => arch/x86}/vga.zig (98%) diff --git a/src/kernel/arch/x86/arch.zig b/src/kernel/arch/x86/arch.zig index cd92cdd..6866309 100644 --- a/src/kernel/arch/x86/arch.zig +++ b/src/kernel/arch/x86/arch.zig @@ -16,9 +16,11 @@ const multiboot = @import("multiboot.zig"); const pmm = @import("pmm.zig"); const vmm = @import("../../vmm.zig"); const log = @import("../../log.zig"); -const tty = @import("../../tty.zig"); +const tty = @import("tty.zig"); +const vga = @import("vga.zig"); const Serial = @import("../../serial.zig").Serial; const panic = @import("../../panic.zig").panic; +const TTY = @import("../../tty.zig").TTY; const MemProfile = mem.MemProfile; /// The virtual end of the kernel code @@ -282,6 +284,25 @@ pub fn initSerial(boot_payload: BootPayload) Serial { }; } +/// +/// Initialise the TTY and construct a TTY instance +/// +/// Arguments: +/// IN boot_payload: BootPayload - The payload passed to the kernel on boot +/// +/// Return: tty.TTY +/// The TTY instance constructed with the information required by the rest of the kernel +/// +pub fn initTTY(boot_payload: BootPayload) TTY { + return .{ + .print = tty.writeString, + .setCursor = tty.setCursor, + .cols = vga.WIDTH, + .rows = vga.HEIGHT, + .clear = tty.clearScreen, + }; +} + /// /// Initialise the system's memory. Populates a memory profile with boot modules from grub, the amount of available memory, the reserved regions of virtual and physical memory as well as the start and end of the kernel code /// @@ -391,6 +412,11 @@ pub fn init(mb_info: *multiboot.multiboot_info_t, mem_profile: *const MemProfile syscalls.init(); enableInterrupts(); + + // Initialise the VGA and TTY here since their tests belong the architecture and so should be a part of the + // arch init test messages + vga.init(); + tty.init(); } test "" { @@ -404,4 +430,7 @@ test "" { _ = @import("rtc.zig"); _ = @import("syscalls.zig"); _ = @import("paging.zig"); + _ = @import("serial.zig"); + _ = @import("tty.zig"); + _ = @import("vga.zig"); } diff --git a/src/kernel/arch/x86/tty.zig b/src/kernel/arch/x86/tty.zig new file mode 100644 index 0000000..2de30bf --- /dev/null +++ b/src/kernel/arch/x86/tty.zig @@ -0,0 +1,2106 @@ +const std = @import("std"); +const fmt = std.fmt; +const builtin = @import("builtin"); +const is_test = builtin.is_test; +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const expectError = std.testing.expectError; +const build_options = @import("build_options"); +const mock_path = build_options.mock_path; +const vga = if (is_test) @import("../../" ++ mock_path ++ "vga_mock.zig") else @import("vga.zig"); +const log = if (is_test) @import("../../" ++ mock_path ++ "log_mock.zig") else @import("../../log.zig"); +const panic = if (is_test) @import("../../" ++ mock_path ++ "panic_mock.zig").panic else @import("../../panic.zig").panic; + +/// The error set for if there is an error whiles printing. +const TtyError = error{ + /// If the printing tries to print outside the video buffer. + OutOfBounds, +}; + +/// 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; + +/// The location of the kernel in virtual memory so can calculate the address of the VGA buffer +extern var KERNEL_ADDR_OFFSET: *u32; + +/// 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: { + 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. +/// +/// 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: TtyError +/// TtyError.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) TtyError!void { + // Secure programming ;) + if (video_buf_offset >= video_buffer.len and + size > video_buffer.len - video_buf_offset and + size > data.len) + { + return TtyError.OutOfBounds; + } + + var i: u32 = 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: +/// TtyError.OutOfBounds - If the size to copy is greater than the size of the pages. +/// +fn pageMove(dest: []u16, src: []u16, size: u16) TtyError!void { + if (dest.len < size or src.len < size) { + return TtyError.OutOfBounds; + } + + // Not an error if 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: u16 = 0; + while (i != size) : (i += 1) { + dest[i] = src[i]; + } + } else { + var i = size; + while (i != 0) { + i -= 1; + dest[i] = src[i]; + } + } +} + +/// +/// Clears a region of the video buffer to a VGA entry from the beginning. +/// +/// 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. +/// +/// Errors: +/// TtyError.OutOfBounds - If the size to copy is greater than the size of the video buffer. +/// +fn setVideoBuffer(c: u16, size: u16) TtyError!void { + if (size > VIDEO_BUFFER_SIZE) { + return TtyError.OutOfBounds; + } + + 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 and set the current column and row (x, y). +/// +fn getCursor() void { + const cursor = vga.getCursor(); + + row = @truncate(u8, cursor / vga.WIDTH); + column = @truncate(u8, cursor % vga.WIDTH); +} + +/// +/// Put a character at a specific column and row position on the screen. This will use the current +/// colour. +/// +/// 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: +/// TtyError.OutOfBounds - If trying to print outside the video buffer. +/// +fn putEntryAt(char: u8, x: u8, y: u8) TtyError!void { + const index = y * vga.WIDTH + x; + + // Bounds check + if (index >= VIDEO_BUFFER_SIZE) { + return TtyError.OutOfBounds; + } + + const char_entry = 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; + try videoCopy(START_OF_DISPLAYABLE_REGION, pages[page_index][0..TOTAL_CHAR_ON_PAGE], TOTAL_CHAR_ON_PAGE); + + // 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. +/// +/// Arguments: +/// IN rows: u16 - The number of rows to move up. +/// +/// Errors: +/// TtyError.OutOfBounds - If trying to move up more rows on a page. +/// +fn pagesMoveRowsUp(rows: u16) TtyError!void { + // Out of bounds check + if (rows > ROW_TOTAL) { + return TtyError.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 = rows * vga.WIDTH; + const chars_to_move = (ROW_TOTAL - rows) * vga.WIDTH; + try pageMove(pages[TOTAL_NUM_PAGES - 1][0..chars_to_move], pages[TOTAL_NUM_PAGES - 1][row_length..], chars_to_move); + + // Loop for the other pages + var i = TOTAL_NUM_PAGES - 1; + while (i > 0) : (i -= 1) { + try pageMove(pages[i][chars_to_move..], pages[i - 1][0..row_length], row_length); + try pageMove(pages[i - 1][0..chars_to_move], pages[i - 1][row_length..], chars_to_move); + } + + // 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. +/// +/// Errors: +/// TtyError.OutOfBounds - If trying to move up more rows on a page. This shouldn't happen +/// as bounds checks have been done. +/// +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) { + const rows_to_move = row - vga.HEIGHT + 1; + + // Move rows up pages by temp, will usually be one. + // TODO: Maybe panic here as we have the check above, so if this fails, then is a big problem + pagesMoveRowsUp(rows_to_move) catch |e| { + panic(@errorReturnTrace(), "Can't move {} rows up. Must be less than {}\n", .{ rows_to_move, ROW_TOTAL }); + }; + + // Move all rows up by rows_to_move + var i: u32 = 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). +/// +/// Arguments: +/// IN char: u8 - The character to print. +/// +/// Errors: +/// TtyError.OutOfBounds - If trying to scroll more rows on a page/displayable region or +/// print beyond the video buffer. +/// +fn putChar(char: u8) TtyError!void { + const column_temp = column; + const row_temp = 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(); + } + }, + } +} + +/// +/// Set the TTY cursor position to a row and column +/// +/// Arguments: +/// IN r: u8 - The row to set it to +/// IN col: u8 - The column to set it to +/// +pub fn setCursor(r: u8, col: u8) void { + column = col; + row = r; + updateCursor(); +} + +/// +/// Print a string to the TTY. This also updates to hardware cursor. +/// +/// Arguments: +/// IN str: []const u8 - The string to print. +/// +/// Errors: +/// TtyError.OutOfBounds - If trying to print beyond the video buffer. +/// +pub fn writeString(str: []const u8) TtyError!void { + // Make sure we update the cursor to the last character + defer updateCursor(); + for (str) |char| { + try putChar(char); + } +} + +/// +/// Move up a page. This will copy the page above to the video buffer. Will keep trace of which +/// page is being displayed. +/// +pub fn pageUp() void { + if (page_index < TOTAL_NUM_PAGES - 1) { + // Copy page to display + page_index += 1; + // Bounds have been checked, so shouldn't error + videoCopy(START_OF_DISPLAYABLE_REGION, pages[page_index][0..TOTAL_CHAR_ON_PAGE], TOTAL_CHAR_ON_PAGE) catch |e| { + log.logError("TTY: Error moving page up. Error: {}\n", .{e}); + }; + 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. +/// +pub fn pageDown() void { + if (page_index > 0) { + // Copy page to display + page_index -= 1; + // Bounds have been checked, so shouldn't error + videoCopy(START_OF_DISPLAYABLE_REGION, pages[page_index][0..TOTAL_CHAR_ON_PAGE], TOTAL_CHAR_ON_PAGE) catch |e| { + log.logError("TTY: Error moving page down. Error: {}\n", .{e}); + }; + + 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. +/// +pub fn clearScreen() void { + // Move all the rows up + // This is within bounds, so shouldn't error + pagesMoveRowsUp(ROW_TOTAL) catch |e| { + log.logError("TTY: Error moving all pages up. Error: {}\n", .{e}); + }; + + // Clear the screen + var i: u16 = START_OF_DISPLAYABLE_REGION; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + video_buffer[i] = blank; + } + + // Set the cursor to below the logo + column = 0; + row = ROW_MIN; + updateCursor(); +} + +/// +/// This moves the software and hardware cursor to the left by one. +/// +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. +/// +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. +/// +/// Arguments: +/// IN 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); +} + +/// +/// Gets the video buffer's virtual address. +/// +/// Return: usize +/// The virtual address of the video buffer +/// +pub fn getVideoBufferAddress() usize { + return @ptrToInt(&KERNEL_ADDR_OFFSET) + 0xB8000; +} + +/// +/// 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. +/// +pub fn init() void { + // Video buffer in higher half + if (is_test) { + video_buffer = @intToPtr([*]volatile u16, mock_getVideoBufferAddress())[0..VIDEO_BUFFER_SIZE]; + } else { + video_buffer = @intToPtr([*]volatile u16, getVideoBufferAddress())[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 = ROW_MIN - (vga.HEIGHT - 1 - row); + } + + // Make a copy into the 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 7 rows down + 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) catch |e| { + log.logError("TTY: Error clearing the top 7 rows. Error: {}\n", .{e}); + }; + row += @truncate(u8, row_offset + ROW_MIN); + } else { + // Clear the screen + setVideoBuffer(blank, VIDEO_BUFFER_SIZE) catch |e| { + log.logError("TTY: Error clearing the screen. Error: {}\n", .{e}); + }; + // Set the row to below the logo + row = ROW_MIN; + } + + if (build_options.rt_test) runtimeTests(); +} + +const test_colour: u8 = vga.orig_entryColour(vga.COLOUR_LIGHT_GREY, vga.COLOUR_BLACK); +var test_video_buffer: [VIDEO_BUFFER_SIZE]u16 = [_]u16{0} ** VIDEO_BUFFER_SIZE; + +fn mock_getVideoBufferAddress() usize { + return @ptrToInt(&test_video_buffer); +} + +fn resetGlobals() void { + column = 0; + row = 0; + page_index = 0; + colour = undefined; + video_buffer = undefined; + 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; + }; +} + +fn setUpVideoBuffer() 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])); + + colour = test_colour; + blank = vga.orig_entry(0, test_colour); +} + +fn setVideoBufferBlankPages() void { + setUpVideoBuffer(); + for (video_buffer) |*b| { + b.* = blank; + } + + setPagesBlank(); +} + +fn setVideoBufferIncrementingBlankPages() void { + setUpVideoBuffer(); + for (video_buffer) |*b, i| { + b.* = @intCast(u16, i); + } + + setPagesBlank(); +} + +fn setPagesBlank() void { + for (pages) |*p_i| { + for (p_i) |*p_j| { + p_j.* = blank; + } + } +} + +fn setPagesIncrementing() void { + for (pages) |*p_i, i| { + for (p_i) |*p_j, j| { + p_j.* = @intCast(u16, i) * TOTAL_CHAR_ON_PAGE + @intCast(u16, j); + } + } +} + +fn defaultVariablesTesting(p_i: u8, r: u8, c: u8) void { + expectEqual(test_colour, colour); + expectEqual(@as(u16, test_colour) << 8, blank); + expectEqual(p_i, page_index); + expectEqual(r, row); + expectEqual(c, column); +} + +fn incrementingPagesTesting() void { + for (pages) |p_i, i| { + for (p_i) |p_j, j| { + expectEqual(i * TOTAL_CHAR_ON_PAGE + j, p_j); + } + } +} + +fn blankPagesTesting() void { + for (pages) |p_i| { + for (p_i) |p_j| { + expectEqual(blank, p_j); + } + } +} + +fn incrementingVideoBufferTesting() void { + var i: u32 = 0; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + const b = video_buffer[i]; + expectEqual(i, b); + } +} + +fn defaultVideoBufferTesting() void { + var i: u32 = 0; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + const b = video_buffer[i]; + expectEqual(vga.orig_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(); + + // Mocking out the vga.updateCursor call for updating the hardware cursor + vga.initTest(); + defer vga.freeTest(); + + vga.addTestParams("updateCursor", .{ @as(u16, 0), @as(u16, 0) }); + + // Pre testing + defaultAllTesting(0, 0, 0); + + // Call function + updateCursor(); + + // Post test + defaultAllTesting(0, 0, 0); + + // Tear down + resetGlobals(); +} + +test "getCursor zero" { + // Set up + setVideoBufferBlankPages(); + + // Mocking out the vga.getCursor call for getting the hardware cursor + vga.initTest(); + defer vga.freeTest(); + + vga.addTestParams("getCursor", .{@as(u16, 0)}); + + // Pre testing + defaultAllTesting(0, 0, 0); + + // Call function + getCursor(); + + // Post test + defaultAllTesting(0, 0, 0); + + // Tear down + resetGlobals(); +} + +test "getCursor EEF" { + // Set up + setVideoBufferBlankPages(); + + // Mocking out the vga.getCursor call for getting the hardware cursor + vga.initTest(); + defer vga.freeTest(); + + vga.addTestParams("getCursor", .{@as(u16, 0x0EEF)}); + + // Pre testing + defaultAllTesting(0, 0, 0); + + // Call function + getCursor(); + + // Post test + defaultAllTesting(0, 47, 63); + + // Tear down + resetGlobals(); +} + +test "putEntryAt out of bounds" { + // Set up + setVideoBufferBlankPages(); + + // Pre testing + defaultAllTesting(0, 0, 0); + + // Call function + expectError(TtyError.OutOfBounds, putEntryAt('A', 100, 100)); + + // Post test + defaultAllTesting(0, 0, 0); + + // Tear down + resetGlobals(); +} + +test "putEntryAt not in displayable region" { + // Set up + setVideoBufferBlankPages(); + + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("entry", vga.orig_entry); + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + // Enable and update cursor is only called once, can can use the consume function call + //vga.addConsumeFunction("enableCursor", vga.mock_enableCursor); + + // Pre testing + defaultAllTesting(0, 0, 0); + + // Call function + const x = 0; + const y = 0; + const char = 'A'; + try putEntryAt(char, x, y); + + // Post test + defaultVariablesTesting(0, 0, 0); + blankPagesTesting(); + + var i: u32 = 0; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + const b = video_buffer[i]; + if (i == y * vga.WIDTH + x) { + expectEqual(vga.orig_entry(char, test_colour), b); + } else { + expectEqual(vga.orig_entry(0, test_colour), b); + } + } + + // Tear down + resetGlobals(); +} + +test "putEntryAt in displayable region page_index is 0" { + // Set up + setVideoBufferBlankPages(); + + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("entry", vga.orig_entry); + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + // Pre testing + defaultAllTesting(0, 0, 0); + + // Call function + const x = 0; + const y = ROW_MIN; + const char = 'A'; + try putEntryAt(char, x, y); + + // 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.orig_entry(char, test_colour), c); + } else { + expectEqual(blank, c); + } + } + } + + var i: u32 = 0; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + const b = video_buffer[i]; + if (i == y * vga.WIDTH + x) { + expectEqual(vga.orig_entry(char, test_colour), b); + } else { + expectEqual(vga.orig_entry(0, test_colour), b); + } + } + + // Tear down + resetGlobals(); +} + +test "putEntryAt in displayable region page_index is not 0" { + // Set up + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("entry", vga.orig_entry); + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + // Enable and update cursor is only called once, can can use the consume function call + vga.addConsumeFunction("enableCursor", vga.mock_enableCursor); + + setVideoBufferBlankPages(); + + // Fill the 1'nd page (index 1) will all 1's + const ones = vga.orig_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(@as(u16, 0), char); + } + } + } + + // Call function + const x = 0; + const y = ROW_MIN; + const char = 'A'; + try putEntryAt(char, x, y); + + // Post test + defaultVariablesTesting(0, 0, 0); + + // Print page number + const text = "Page 0 of 4"; + const column_temp = column; + const row_temp = row; + column = @truncate(u8, vga.WIDTH) - @truncate(u8, text.len); + row = ROW_MIN - 1; + writeString(text) catch |e| { + log.logError("TTY: Unable to print page number, printing out of bounds. Error: {}\n", .{e}); + }; + column = column_temp; + row = row_temp; + + for (pages) |page, i| { + for (page) |c, j| { + if (i == 0 and j == 0) { + expectEqual(vga.orig_entry(char, test_colour), c); + } else if (i == 0) { + expectEqual(ones, c); + } else { + expectEqual(@as(u16, 0), c); + } + } + } + + // The top 7 rows won't be copied + var i: u32 = 0; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + const b = video_buffer[i]; + if (i < START_OF_DISPLAYABLE_REGION - 11) { + expectEqual(blank, b); + } else if (i < START_OF_DISPLAYABLE_REGION) { + expectEqual(vga.orig_entry(text[i + 11 - START_OF_DISPLAYABLE_REGION], colour), b); + } else if (i == y * vga.WIDTH + x) { + expectEqual(vga.orig_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 = ROW_TOTAL + 1; + expectError(TtyError.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 = 0; + try pagesMoveRowsUp(rows_to_move); + + // 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 = 1; + try pagesMoveRowsUp(rows_to_move); + + // Post test + defaultVariablesTesting(0, 0, 0); + defaultVideoBufferTesting(); + + const to_add = rows_to_move * vga.WIDTH; + for (pages) |page, i| { + for (page) |c, j| { + if (j >= TOTAL_CHAR_ON_PAGE - to_add) { + if (i == 0) { + // The last rows will be blanks + expectEqual(blank, c); + } else { + expectEqual((i - 1) * TOTAL_CHAR_ON_PAGE + (j + to_add - TOTAL_CHAR_ON_PAGE), c); + } + } else { + // All rows moved up one, so add vga.WIDTH + expectEqual(i * TOTAL_CHAR_ON_PAGE + j + to_add, c); + } + } + } + + // 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 = ROW_TOTAL - 1; + try pagesMoveRowsUp(rows_to_move); + + // Post test + defaultVariablesTesting(0, 0, 0); + defaultVideoBufferTesting(); + + const to_add = rows_to_move * vga.WIDTH; + for (pages) |page, i| { + for (page) |c, j| { + if (j >= TOTAL_CHAR_ON_PAGE - to_add) { + if (i == 0) { + // The last rows will be blanks + expectEqual(blank, c); + } else { + expectEqual((i - 1) * TOTAL_CHAR_ON_PAGE + (j + to_add - TOTAL_CHAR_ON_PAGE), c); + } + } else { + // All rows moved up one, so add vga.WIDTH + expectEqual(i * TOTAL_CHAR_ON_PAGE + j + to_add, c); + } + } + } + + // 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 = ROW_TOTAL; + try pagesMoveRowsUp(rows_to_move); + + // Post test + defaultVariablesTesting(0, 0, 0); + defaultVideoBufferTesting(); + + for (pages) |page, i| { + for (page) |c, j| { + if (i == 0) { + // The last rows will be blanks + expectEqual(blank, c); + } else { + expectEqual((i - 1) * TOTAL_CHAR_ON_PAGE + j, c); + } + } + } + + // 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 = 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); + + const to_add = (row_test - vga.HEIGHT + 1) * vga.WIDTH; + for (pages) |page, i| { + for (page) |c, j| { + if (j >= TOTAL_CHAR_ON_PAGE - to_add) { + if (i == 0) { + // The last rows will be blanks + expectEqual(blank, c); + } else { + expectEqual((i - 1) * TOTAL_CHAR_ON_PAGE + (j + to_add - TOTAL_CHAR_ON_PAGE), c); + } + } else { + // All rows moved up one, so add vga.WIDTH + expectEqual(i * TOTAL_CHAR_ON_PAGE + j + to_add, c); + } + } + } + + var i: u32 = 0; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + const buf = video_buffer[i]; + if (i < START_OF_DISPLAYABLE_REGION) { + expectEqual(i, buf); + } else if (i >= VIDEO_BUFFER_SIZE - to_add) { + expectEqual(blank, buf); + } else { + expectEqual(i + to_add, buf); + } + } + + // Tear down + resetGlobals(); +} + +test "scroll row is more than height" { + // Set up + setVideoBufferIncrementingBlankPages(); + setPagesIncrementing(); + + const row_test = 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); + + const to_add = (row_test - vga.HEIGHT + 1) * vga.WIDTH; + for (pages) |page, i| { + for (page) |c, j| { + if (j >= TOTAL_CHAR_ON_PAGE - to_add) { + if (i == 0) { + // The last rows will be blanks + expectEqual(blank, c); + } else { + expectEqual((i - 1) * TOTAL_CHAR_ON_PAGE + (j + to_add - TOTAL_CHAR_ON_PAGE), c); + } + } else { + // All rows moved up one, so add vga.WIDTH + expectEqual(i * TOTAL_CHAR_ON_PAGE + j + to_add, c); + } + } + } + + var i: u32 = 0; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + const buf = video_buffer[i]; + if (i < START_OF_DISPLAYABLE_REGION) { + expectEqual(i, buf); + } else if (i >= VIDEO_BUFFER_SIZE - to_add) { + expectEqual(blank, buf); + } else { + expectEqual(i + to_add, buf); + } + } + + // Tear down + resetGlobals(); +} + +test "putChar new line within screen" { + // Set up + setVideoBufferBlankPages(); + + // Pre testing + column = 5; + row = 5; + defaultAllTesting(0, 5, 5); + + // Call function + try putChar('\n'); + + // 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 + try putChar('\n'); + + // 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 + try putChar('\t'); + + // 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 + try putChar('\t'); + + // 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 + try putChar('\t'); + + // 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 + try putChar('\r'); + + // 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 + try putChar('\x08'); + + // 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 + try putChar('\x08'); + + // 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 + try putChar('\x08'); + + // Post test + defaultAllTesting(0, 0, vga.WIDTH - 1); + + // Tear down + resetGlobals(); +} + +test "putChar any char in row" { + // Set up + setVideoBufferBlankPages(); + + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("entry", vga.orig_entry); + + // Pre testing + defaultAllTesting(0, 0, 0); + + // Call function + try putChar('A'); + + // Post test + defaultVariablesTesting(0, 0, 1); + blankPagesTesting(); + + var i: u32 = 0; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + const buf = video_buffer[i]; + if (i == 0) { + expectEqual(vga.orig_entry('A', colour), buf); + } else { + expectEqual(blank, buf); + } + } + + // Tear down + resetGlobals(); +} + +test "putChar any char end of row" { + // Set up + setVideoBufferBlankPages(); + + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("entry", vga.orig_entry); + + // Pre testing + column = vga.WIDTH - 1; + defaultAllTesting(0, 0, vga.WIDTH - 1); + + // Call function + try putChar('A'); + + // Post test + defaultVariablesTesting(0, 1, 0); + blankPagesTesting(); + + var i: u32 = 0; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + const buf = video_buffer[i]; + if (i == vga.WIDTH - 1) { + expectEqual(vga.orig_entry('A', colour), buf); + } else { + expectEqual(blank, buf); + } + } + + // Tear down + resetGlobals(); +} + +test "putChar any char end of screen" { + // Set up + setVideoBufferBlankPages(); + + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("entry", vga.orig_entry); + + // Pre testing + row = vga.HEIGHT - 1; + column = vga.WIDTH - 1; + defaultAllTesting(0, vga.HEIGHT - 1, vga.WIDTH - 1); + + // Call function + try putChar('A'); + + // 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.orig_entry('A', colour), c); + } else { + expectEqual(blank, c); + } + } + } + + var i: u32 = 0; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + const buf = video_buffer[i]; + if (i == VIDEO_BUFFER_SIZE - vga.WIDTH - 1) { + expectEqual(vga.orig_entry('A', colour), buf); + } else { + expectEqual(blank, buf); + } + } + + // 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(); + + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("entry", vga.orig_entry); + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + vga.addConsumeFunction("disableCursor", vga.mock_disableCursor); + + // Pre testing + defaultVariablesTesting(0, 0, 0); + incrementingPagesTesting(); + defaultVideoBufferTesting(); + + // Call function + pageUp(); + + // Post test + defaultVariablesTesting(1, 0, 0); + incrementingPagesTesting(); + + // Print page number + const text = "Page 1 of 4"; + const column_temp = column; + const row_temp = row; + column = @truncate(u8, vga.WIDTH) - @truncate(u8, text.len); + row = ROW_MIN - 1; + writeString(text) catch |e| { + log.logError("TTY: Unable to print page number, printing out of bounds. Error: {}\n", .{e}); + }; + column = column_temp; + row = row_temp; + + var i: u32 = 0; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + const b = video_buffer[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.orig_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(); + + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("entry", vga.orig_entry); + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + vga.addConsumeFunction("disableCursor", vga.mock_disableCursor); + + // 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(); + + // Print page number + const text = "Page 3 of 4"; + const column_temp = column; + const row_temp = row; + column = @truncate(u8, vga.WIDTH) - @truncate(u8, text.len); + row = ROW_MIN - 1; + writeString(text) catch |e| { + log.logError("TTY: Unable to print page number, printing out of bounds. Error: {}\n", .{e}); + }; + column = column_temp; + row = row_temp; + + var i: u32 = 0; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + const b = video_buffer[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.orig_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" { + // Set up + setVideoBufferIncrementingBlankPages(); + setPagesIncrementing(); + + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + // Pre testing + defaultVariablesTesting(0, 0, 0); + incrementingVideoBufferTesting(); + incrementingPagesTesting(); + + // Call function + clearScreen(); + + // Post test + defaultVariablesTesting(0, ROW_MIN, 0); + var i: u32 = 0; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + const buf = video_buffer[i]; + if (i < START_OF_DISPLAYABLE_REGION) { + expectEqual(i, buf); + } else { + expectEqual(blank, buf); + } + } + + for (pages) |page, j| { + for (page) |c, k| { + if (j == 0) { + // The last rows will be blanks + expectEqual(blank, c); + } else { + expectEqual((j - 1) * TOTAL_CHAR_ON_PAGE + k, c); + } + } + } + + // Tear down + resetGlobals(); +} + +test "moveCursorLeft top left of screen" { + // Set up + setVideoBufferBlankPages(); + + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + // 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(); + + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + // 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(); + + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + // 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(); + + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + // 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(); + + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + // 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(); + + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + // 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" { + // Set up + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addConsumeFunction("entry", vga.orig_entry); + + // Pre testing + + // Call function + const new_colour = vga.orig_entryColour(vga.COLOUR_WHITE, vga.COLOUR_WHITE); + setColour(new_colour); + + // Post test + expectEqual(new_colour, colour); + expectEqual(vga.orig_entry(0, new_colour), blank); + + // Tear down + resetGlobals(); +} + +test "writeString" { + // Set up + setVideoBufferBlankPages(); + + // Mocking out the vga calls + vga.initTest(); + defer vga.freeTest(); + + vga.addRepeatFunction("entry", vga.orig_entry); + + vga.addConsumeFunction("updateCursor", vga.mock_updateCursor); + + // Pre testing + row = ROW_MIN; + defaultAllTesting(0, ROW_MIN, 0); + + // Call function + try writeString("ABC"); + + // Post test + defaultVariablesTesting(0, ROW_MIN, 3); + for (pages) |page, i| { + for (page) |c, j| { + if ((i == 0) and (j == 0)) { + expectEqual(vga.orig_entry('A', colour), c); + } else if ((i == 0) and (j == 1)) { + expectEqual(vga.orig_entry('B', colour), c); + } else if ((i == 0) and (j == 2)) { + expectEqual(vga.orig_entry('C', colour), c); + } else { + expectEqual(blank, c); + } + } + } + + var i: u32 = 0; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + const buf = video_buffer[i]; + if (i == START_OF_DISPLAYABLE_REGION) { + expectEqual(vga.orig_entry('A', colour), buf); + } else if (i == START_OF_DISPLAYABLE_REGION + 1) { + expectEqual(vga.orig_entry('B', colour), buf); + } else if (i == START_OF_DISPLAYABLE_REGION + 2) { + expectEqual(vga.orig_entry('C', colour), buf); + } else { + expectEqual(blank, buf); + } + } + + // Tear down + resetGlobals(); +} + +test "init 0,0" { + // Set up + setVideoBufferBlankPages(); + + // Mocking out the vga.updateCursor call for updating the hardware cursor + vga.initTest(); + defer vga.freeTest(); + + vga.addTestParams("getCursor", .{@as(u16, 0)}); + + vga.addRepeatFunction("entryColour", vga.orig_entryColour); + vga.addRepeatFunction("entry", vga.orig_entry); + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + vga.addConsumeFunction("enableCursor", vga.mock_enableCursor); + + // Pre testing + defaultAllTesting(0, 0, 0); + + // Call function + init(); + + // Post test + defaultVariablesTesting(0, ROW_MIN, 0); + blankPagesTesting(); + + var i: u32 = 0; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + const buf = video_buffer[i]; + if (i < 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, buf); + } + } + + // Tear down + resetGlobals(); +} + +test "init not 0,0" { + // Set up + setVideoBufferBlankPages(); + + // Mocking out the vga.updateCursor call for updating the hardware cursor + vga.initTest(); + defer vga.freeTest(); + + vga.addTestParams("getCursor", .{vga.WIDTH}); + + vga.addRepeatFunction("entryColour", vga.orig_entryColour); + vga.addRepeatFunction("entry", vga.orig_entry); + vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); + + vga.addConsumeFunction("enableCursor", vga.mock_enableCursor); + + // Pre testing + defaultAllTesting(0, 0, 0); + + // Call function + init(); + + // Post test + defaultVariablesTesting(0, ROW_MIN + 1, 0); + blankPagesTesting(); + + var i: u32 = 0; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + const buf = video_buffer[i]; + if (i < 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, buf); + } + } + + // Tear down + resetGlobals(); +} + +/// +/// Test the init function set up everything properly. +/// +fn rt_initialisedGlobals() void { + if (@ptrToInt(video_buffer.ptr) != @ptrToInt(&KERNEL_ADDR_OFFSET) + 0xB8000) { + panic(@errorReturnTrace(), "Video buffer not at correct virtual address, found: {}\n", .{@ptrToInt(video_buffer.ptr)}); + } + + if (page_index != 0) { + panic(@errorReturnTrace(), "Page index not at zero, found: {}\n", .{page_index}); + } + + if (colour != vga.entryColour(vga.COLOUR_LIGHT_GREY, vga.COLOUR_BLACK)) { + panic(@errorReturnTrace(), "Colour not set up properly, found: {}\n", .{colour}); + } + + if (blank != vga.entry(0, colour)) { + panic(@errorReturnTrace(), "Blank not set up properly, found: {}\n", .{blank}); + } + + // Make sure the screen isn't all blank + var all_blank = true; + var i: u32 = 0; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + const buf = video_buffer[i]; + if (buf != blank and buf != 0) { + all_blank = false; + break; + } + } + + if (all_blank) { + panic(@errorReturnTrace(), "Screen all blank, should have logo and page number\n", .{}); + } + + log.logInfo("TTY: Tested globals\n", .{}); +} + +/// +/// Test printing a string will output to the screen. This will check both the video memory and +/// the pages. +/// +fn rt_printString() void { + const text = "abcdefg"; + const clear_text = "\x08" ** text.len; + + writeString(text) catch |e| panic(@errorReturnTrace(), "Failed to print string to tty: {}\n", .{e}); + + // Check the video memory + var counter: u32 = 0; + var i: u32 = 0; + while (i < VIDEO_BUFFER_SIZE) : (i += 1) { + const buf = video_buffer[i]; + if (counter < text.len and buf == vga.entry(text[counter], colour)) { + counter += 1; + } else if (counter == text.len) { + // Found all the text + break; + } else { + counter = 0; + } + } + + if (counter != text.len) { + panic(@errorReturnTrace(), "Didn't find the printed text in video memory\n", .{}); + } + + // Check the pages + counter = 0; + for (pages[0]) |c| { + if (counter < text.len and c == vga.entry(text[counter], colour)) { + counter += 1; + } else if (counter == text.len) { + // Found all the text + break; + } else { + counter = 0; + } + } + + if (counter != text.len) { + panic(@errorReturnTrace(), "Didn't find the printed text in pages\n", .{}); + } + + // Clear the text + writeString(clear_text) catch |e| panic(@errorReturnTrace(), "Failed to print string to tty: {}\n", .{e}); + + log.logInfo("TTY: Tested printing\n", .{}); +} + +/// +/// Run all the runtime tests. +/// +fn runtimeTests() void { + rt_initialisedGlobals(); + rt_printString(); +} diff --git a/src/kernel/vga.zig b/src/kernel/arch/x86/vga.zig similarity index 98% rename from src/kernel/vga.zig rename to src/kernel/arch/x86/vga.zig index c0fed7d..74a25ec 100644 --- a/src/kernel/vga.zig +++ b/src/kernel/arch/x86/vga.zig @@ -3,10 +3,9 @@ const builtin = @import("builtin"); const is_test = builtin.is_test; const expectEqual = std.testing.expectEqual; const build_options = @import("build_options"); -const mock_path = build_options.mock_path; -const arch = @import("arch.zig").internals; -const log = if (is_test) @import(mock_path ++ "log_mock.zig") else @import("log.zig"); -const panic = @import("panic.zig").panic; +const arch = if (is_test) @import(build_options.arch_mock_path ++ "arch_mock.zig") else @import("arch.zig"); +const log = if (is_test) @import(build_options.arch_mock_path ++ "log_mock.zig") else @import("../../log.zig"); +const panic = @import("../../panic.zig").panic; /// The port address for the VGA register selection. const PORT_ADDRESS: u16 = 0x03D4; diff --git a/src/kernel/kmain.zig b/src/kernel/kmain.zig index f496994..0b73c20 100644 --- a/src/kernel/kmain.zig +++ b/src/kernel/kmain.zig @@ -55,8 +55,6 @@ export fn kmain(boot_payload: arch.BootPayload) void { arch.init(boot_payload, &mem_profile, &fixed_allocator.allocator); log.logInfo("Arch init done\n", .{}); - vga.init(); - tty.init(); // Give the kernel heap 10% of the available memory. This can be fine-tuned as time goes on. var heap_size = mem_profile.mem_kb / 10 * 1024; // The heap size must be a power of two so find the power of two smaller than or equal to the heap_size @@ -66,8 +64,21 @@ export fn kmain(boot_payload: arch.BootPayload) void { var kernel_heap = heap.init(arch.VmmPayload, &kernel_vmm, vmm.Attributes{ .kernel = true, .writable = true, .cachable = true }, heap_size, &fixed_allocator.allocator) catch |e| { panic_root.panic(@errorReturnTrace(), "Failed to initialise kernel heap: {}\n", .{e}); }; + tty.init(&kernel_heap.allocator, boot_payload); + log.logInfo("Init done\n", .{}); + tty.clear(); + const logo = + \\ _____ _ _ _ _______ ____ + \\ | __ \ | | | | | | |__ __| / __ \ + \\ | |__) | | | | | | | | | | | | | + \\ | ___/ | | | | | | | | | | | | + \\ | | | |____ | |__| | | | | |__| | + \\ |_| |______| \____/ |_| \____/ + ; + tty.print("{}\n\n", .{logo}); + tty.print("Hello Pluto from kernel :)\n", .{}); // The panic runtime tests must run last as they never return diff --git a/src/kernel/tty.zig b/src/kernel/tty.zig index 2dc6724..0d54f03 100644 --- a/src/kernel/tty.zig +++ b/src/kernel/tty.zig @@ -1,437 +1,43 @@ -const builtin = @import("builtin"); -const is_test = builtin.is_test; const std = @import("std"); const fmt = std.fmt; -const expect = std.testing.expect; -const expectEqual = std.testing.expectEqual; -const expectError = std.testing.expectError; +const Allocator = std.mem.Allocator; const build_options = @import("build_options"); -const mock_path = build_options.mock_path; -const vga = if (is_test) @import(mock_path ++ "vga_mock.zig") else @import("vga.zig"); -const log = if (is_test) @import(mock_path ++ "log_mock.zig") else @import("log.zig"); -const panic = if (is_test) @import(mock_path ++ "panic_mock.zig").panic else @import("panic.zig").panic; +const arch = @import("arch.zig").internals; +const log = @import("log.zig"); +const panic = @import("panic.zig").panic; /// The OutStream for the format function -const OutStream = std.io.OutStream(void, TtyError, printCallback); +const OutStream = std.io.OutStream(void, anyerror, printCallback); -/// The error set for if there is an error whiles printing. -const TtyError = error{ - /// If the printing tries to print outside the video buffer. - OutOfBounds, +pub const TTY = struct { + /// Print a already-formatted string + print: fn ([]const u8) anyerror!void, + /// Set the TTY cursor position to a row and column + setCursor: fn (u8, u8) void, + /// Clear the screen and set the cursor to top left. The default implementation will be used if null + clear: ?fn () void, + /// The number of character rows supported + rows: u8, + /// The number of character columns supported + cols: u8, }; -/// 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; - -/// The location of the kernel in virtual memory so can calculate the address of the VGA buffer -extern var KERNEL_ADDR_OFFSET: *u32; - -/// 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: { - 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; +/// The current tty stream +var tty: TTY = undefined; +var allocator: *Allocator = undefined; /// -/// Copies data into the video buffer. This is used for copying a page into the 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: TtyError -/// TtyError.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) TtyError!void { - // Secure programming ;) - if (video_buf_offset >= video_buffer.len and - size > video_buffer.len - video_buf_offset and - size > data.len) - { - return TtyError.OutOfBounds; - } - - var i: u32 = 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: -/// TtyError.OutOfBounds - If the size to copy is greater than the size of the pages. -/// -fn pageMove(dest: []u16, src: []u16, size: u16) TtyError!void { - if (dest.len < size or src.len < size) { - return TtyError.OutOfBounds; - } - - // Not an error if 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: u16 = 0; - while (i != size) : (i += 1) { - dest[i] = src[i]; - } - } else { - var i = size; - while (i != 0) { - i -= 1; - dest[i] = src[i]; - } - } -} - -/// -/// Clears a region of the video buffer to a VGA entry from the beginning. -/// -/// 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. -/// -/// Errors: -/// TtyError.OutOfBounds - If the size to copy is greater than the size of the video buffer. -/// -fn setVideoBuffer(c: u16, size: u16) TtyError!void { - if (size > VIDEO_BUFFER_SIZE) { - return TtyError.OutOfBounds; - } - - 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 and set the current column and row (x, y). -/// -fn getCursor() void { - const cursor = vga.getCursor(); - - row = @truncate(u8, cursor / vga.WIDTH); - column = @truncate(u8, cursor % vga.WIDTH); -} - -/// -/// Display the current page number at the bottom right corner. If there was an error with this, -/// then the page number may not be printed and a error log will be emitted. -/// -fn displayPageNumber() void { - const column_temp = column; - const row_temp = row; - - defer column = column_temp; - defer row = row_temp; - - var text_buf = [_]u8{0} ** vga.WIDTH; - - // Formate the page number string so can work out the right alignment. - const fmt_text = fmt.bufPrint(text_buf[0..], "Page {} of {}", .{ page_index, TOTAL_NUM_PAGES - 1 }) catch |e| { - log.logError("TTY: Unable to print page number, buffer too small. Error: {}\n", .{e}); - return; - }; - - // TODO: #89 TTY - print string with alignment - // When print a string with alignment is available, can remove. - // But for now we can calculate the alignment. - column = @truncate(u8, vga.WIDTH) - @truncate(u8, fmt_text.len); - row = ROW_MIN - 1; - - writeString(fmt_text) catch |e| { - log.logError("TTY: Unable to print page number, printing out of bounds. Error: {}\n", .{e}); - }; -} - -/// -/// Put a character at a specific column and row position on the screen. This will use the current -/// colour. -/// -/// 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: -/// TtyError.OutOfBounds - If trying to print outside the video buffer. -/// -fn putEntryAt(char: u8, x: u8, y: u8) TtyError!void { - const index = y * vga.WIDTH + x; - - // Bounds check - if (index >= VIDEO_BUFFER_SIZE) { - return TtyError.OutOfBounds; - } - - const char_entry = 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; - try videoCopy(START_OF_DISPLAYABLE_REGION, pages[page_index][0..TOTAL_CHAR_ON_PAGE], TOTAL_CHAR_ON_PAGE); - 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. -/// -/// Arguments: -/// IN rows: u16 - The number of rows to move up. -/// -/// Errors: -/// TtyError.OutOfBounds - If trying to move up more rows on a page. -/// -fn pagesMoveRowsUp(rows: u16) TtyError!void { - // Out of bounds check - if (rows > ROW_TOTAL) { - return TtyError.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 = rows * vga.WIDTH; - const chars_to_move = (ROW_TOTAL - rows) * vga.WIDTH; - try pageMove(pages[TOTAL_NUM_PAGES - 1][0..chars_to_move], pages[TOTAL_NUM_PAGES - 1][row_length..], chars_to_move); - - // Loop for the other pages - var i = TOTAL_NUM_PAGES - 1; - while (i > 0) : (i -= 1) { - try pageMove(pages[i][chars_to_move..], pages[i - 1][0..row_length], row_length); - try pageMove(pages[i - 1][0..chars_to_move], pages[i - 1][row_length..], chars_to_move); - } - - // 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. -/// -/// Errors: -/// TtyError.OutOfBounds - If trying to move up more rows on a page. This shouldn't happen -/// as bounds checks have been done. -/// -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) { - const rows_to_move = row - vga.HEIGHT + 1; - - // Move rows up pages by temp, will usually be one. - // TODO: Maybe panic here as we have the check above, so if this fails, then is a big problem - pagesMoveRowsUp(rows_to_move) catch |e| { - panic(@errorReturnTrace(), "Can't move {} rows up. Must be less than {}\n", .{ rows_to_move, ROW_TOTAL }); - }; - - // Move all rows up by rows_to_move - var i: u32 = 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). -/// -/// Arguments: -/// IN char: u8 - The character to print. -/// -/// Errors: -/// TtyError.OutOfBounds - If trying to scroll more rows on a page/displayable region or -/// print beyond the video buffer. -/// -fn putChar(char: u8) TtyError!void { - const column_temp = column; - const row_temp = 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. -/// -/// Arguments: -/// IN str: []const u8 - The string to print. -/// -/// Errors: -/// TtyError.OutOfBounds - If trying to print beyond the video buffer. -/// -fn writeString(str: []const u8) TtyError!void { - // Make sure we update the cursor to the last character - defer updateCursor(); - for (str) |char| { - try putChar(char); - } -} - -/// -/// Print the pluto logo. -/// -fn printLogo() void { - const column_temp = column; - const row_temp = row; - - defer column = column_temp; - defer row = row_temp; - - const logo = - \\ _____ _ _ _ _______ ____ - \\ | __ \ | | | | | | |__ __| / __ \ - \\ | |__) | | | | | | | | | | | | | - \\ | ___/ | | | | | | | | | | | | - \\ | | | |____ | |__| | | | | |__| | - \\ |_| |______| \____/ |_| \____/ - ; - - // Print the logo at the top of the screen - column = 0; - row = 0; - - writeString(logo) catch |e| { - log.logError("TTY: Error print logo. Error {}\n", .{e}); - }; -} - -/// -/// A call back function for use in the formation of a string. This calls writeString normally. +/// A call back function for use in the formation of a string. This calls the architecture's print function. /// /// Arguments: /// IN ctx: void - The context of the printing. This will be empty. /// IN str: []const u8 - The string to print. /// -/// Errors: -/// TtyError.OutOfBounds - If trying to print beyond the video buffer. +/// Return: usize +/// The number of characters printed /// -fn printCallback(ctx: void, str: []const u8) TtyError!usize { - try writeString(str); +fn printCallback(ctx: void, str: []const u8) !usize { + tty.print(str) catch |e| panic(@errorReturnTrace(), "Failed to print to tty: {}\n", .{e}); return str.len; } @@ -451,1794 +57,53 @@ pub fn print(comptime format: []const u8, args: var) void { } /// -/// Move up a page. This will copy the page above to the video buffer. Will keep trace of which -/// page is being displayed. +/// Clear the screen by printing a space at each cursor position. Sets the cursor to the top left (0, 0) /// -pub fn pageUp() void { - if (page_index < TOTAL_NUM_PAGES - 1) { - // Copy page to display - page_index += 1; - // Bounds have been checked, so shouldn't error - videoCopy(START_OF_DISPLAYABLE_REGION, pages[page_index][0..TOTAL_CHAR_ON_PAGE], TOTAL_CHAR_ON_PAGE) catch |e| { - log.logError("TTY: Error moving page up. Error: {}\n", .{e}); - }; - 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. -/// -pub fn pageDown() void { - if (page_index > 0) { - // Copy page to display - page_index -= 1; - // Bounds have been checked, so shouldn't error - videoCopy(START_OF_DISPLAYABLE_REGION, pages[page_index][0..TOTAL_CHAR_ON_PAGE], TOTAL_CHAR_ON_PAGE) catch |e| { - log.logError("TTY: Error moving page down. Error: {}\n", .{e}); - }; - - 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. -/// -pub fn clearScreen() void { - // Move all the rows up - // This is within bounds, so shouldn't error - pagesMoveRowsUp(ROW_TOTAL) catch |e| { - log.logError("TTY: Error moving all pages up. Error: {}\n", .{e}); - }; - - // Clear the screen - var i: u16 = START_OF_DISPLAYABLE_REGION; - while (i < VIDEO_BUFFER_SIZE) : (i += 1) { - video_buffer[i] = blank; - } - - // Set the cursor to below the logo - column = 0; - row = ROW_MIN; - updateCursor(); -} - -/// -/// This moves the software and hardware cursor to the left by one. -/// -pub fn moveCursorLeft() void { - if (column == 0) { - if (row != 0) { - column = vga.WIDTH - 1; - row -= 1; - } +pub fn clear() void { + if (tty.clear) |clr| { + clr(); } else { - column -= 1; - } + // Try to allocate the number of spaces for a whole row to avoid calling print too many times + var spaces = allocator.alloc(u8, tty.cols + 1) catch |e| switch (e) { + Allocator.Error.OutOfMemory => { + var row: u8 = 0; + // If we can't allocate the spaces then try the unoptimised way instead + while (row < tty.rows) : (row += 1) { + var col: u8 = 0; + while (col < tty.cols) : (col += 1) { + print(" ", .{}); + } + print("\n", .{}); + } + tty.setCursor(0, 0); + return; + }, + }; + defer allocator.free(spaces); - updateCursor(); -} - -/// -/// This moves the software and hardware cursor to the right by one. -/// -pub fn moveCursorRight() void { - if (column == (vga.WIDTH - 1)) { - if (row != (vga.HEIGHT - 1)) { - column = 0; - row += 1; + var col: u8 = 0; + while (col < tty.cols) : (col += 1) { + spaces[col] = " "[0]; } - } else { - column += 1; + spaces[col] = "\n"[0]; + var row: u8 = 0; + while (row < tty.rows) : (row += 1) { + print("{}", .{spaces}); + } + tty.setCursor(0, 0); } - - 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. +/// Initialise the TTY. The details of which are up to the architecture /// /// Arguments: -/// IN new_colour: u8 - The new foreground and background colour of the screen. +/// IN alloc: *std.mem.Allocator - The allocator to use when requiring memory +/// IN boot_payload: arch.BootPayload - The payload passed to the kernel on boot /// -pub fn setColour(new_colour: u8) void { - colour = new_colour; - blank = vga.entry(0, colour); -} - -/// -/// Gets the video buffer's virtual address. -/// -/// Return: usize -/// The virtual address of the video buffer -/// -pub fn getVideoBufferAddress() usize { - return @ptrToInt(&KERNEL_ADDR_OFFSET) + 0xB8000; -} - -/// -/// 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. -/// -pub fn init() void { +pub fn init(alloc: *Allocator, boot_payload: arch.BootPayload) void { log.logInfo("Init tty\n", .{}); defer log.logInfo("Done tty\n", .{}); - - // Video buffer in higher half - if (is_test) { - video_buffer = @intToPtr([*]volatile u16, mock_getVideoBufferAddress())[0..VIDEO_BUFFER_SIZE]; - } else { - video_buffer = @intToPtr([*]volatile u16, getVideoBufferAddress())[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 = ROW_MIN - (vga.HEIGHT - 1 - row); - } - - // Make a copy into the 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 7 rows down - 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) catch |e| { - log.logError("TTY: Error clearing the top 7 rows. Error: {}\n", .{e}); - }; - row += @truncate(u8, row_offset + ROW_MIN); - } else { - // Clear the screen - setVideoBuffer(blank, VIDEO_BUFFER_SIZE) catch |e| { - log.logError("TTY: Error clearing the screen. Error: {}\n", .{e}); - }; - // Set the row to below the logo - row = ROW_MIN; - } - - printLogo(); - displayPageNumber(); - updateCursor(); - - if (build_options.rt_test) runtimeTests(); -} - -const test_colour: u8 = vga.orig_entryColour(vga.COLOUR_LIGHT_GREY, vga.COLOUR_BLACK); -var test_video_buffer: [VIDEO_BUFFER_SIZE]u16 = [_]u16{0} ** VIDEO_BUFFER_SIZE; - -fn mock_getVideoBufferAddress() usize { - return @ptrToInt(&test_video_buffer); -} - -fn resetGlobals() void { - column = 0; - row = 0; - page_index = 0; - colour = undefined; - video_buffer = undefined; - 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; - }; -} - -fn setUpVideoBuffer() 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])); - - colour = test_colour; - blank = vga.orig_entry(0, test_colour); -} - -fn setVideoBufferBlankPages() void { - setUpVideoBuffer(); - for (video_buffer) |*b| { - b.* = blank; - } - - setPagesBlank(); -} - -fn setVideoBufferIncrementingBlankPages() void { - setUpVideoBuffer(); - for (video_buffer) |*b, i| { - b.* = @intCast(u16, i); - } - - setPagesBlank(); -} - -fn setPagesBlank() void { - for (pages) |*p_i| { - for (p_i) |*p_j| { - p_j.* = blank; - } - } -} - -fn setPagesIncrementing() void { - for (pages) |*p_i, i| { - for (p_i) |*p_j, j| { - p_j.* = @intCast(u16, i) * TOTAL_CHAR_ON_PAGE + @intCast(u16, j); - } - } -} - -fn defaultVariablesTesting(p_i: u8, r: u8, c: u8) void { - expectEqual(test_colour, colour); - expectEqual(@as(u16, test_colour) << 8, blank); - expectEqual(p_i, page_index); - expectEqual(r, row); - expectEqual(c, column); -} - -fn incrementingPagesTesting() void { - for (pages) |p_i, i| { - for (p_i) |p_j, j| { - expectEqual(i * TOTAL_CHAR_ON_PAGE + j, p_j); - } - } -} - -fn blankPagesTesting() void { - for (pages) |p_i| { - for (p_i) |p_j| { - expectEqual(blank, p_j); - } - } -} - -fn incrementingVideoBufferTesting() void { - var i: u32 = 0; - while (i < VIDEO_BUFFER_SIZE) : (i += 1) { - const b = video_buffer[i]; - expectEqual(i, b); - } -} - -fn defaultVideoBufferTesting() void { - var i: u32 = 0; - while (i < VIDEO_BUFFER_SIZE) : (i += 1) { - const b = video_buffer[i]; - expectEqual(vga.orig_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(); - - // Mocking out the vga.updateCursor call for updating the hardware cursor - vga.initTest(); - defer vga.freeTest(); - - vga.addTestParams("updateCursor", .{ @as(u16, 0), @as(u16, 0) }); - - // Pre testing - defaultAllTesting(0, 0, 0); - - // Call function - updateCursor(); - - // Post test - defaultAllTesting(0, 0, 0); - - // Tear down - resetGlobals(); -} - -test "getCursor zero" { - // Set up - setVideoBufferBlankPages(); - - // Mocking out the vga.getCursor call for getting the hardware cursor - vga.initTest(); - defer vga.freeTest(); - - vga.addTestParams("getCursor", .{@as(u16, 0)}); - - // Pre testing - defaultAllTesting(0, 0, 0); - - // Call function - getCursor(); - - // Post test - defaultAllTesting(0, 0, 0); - - // Tear down - resetGlobals(); -} - -test "getCursor EEF" { - // Set up - setVideoBufferBlankPages(); - - // Mocking out the vga.getCursor call for getting the hardware cursor - vga.initTest(); - defer vga.freeTest(); - - vga.addTestParams("getCursor", .{@as(u16, 0x0EEF)}); - - // Pre testing - defaultAllTesting(0, 0, 0); - - // Call function - getCursor(); - - // Post test - defaultAllTesting(0, 47, 63); - - // Tear down - resetGlobals(); -} - -test "displayPageNumber column and row is reset" { - // Set up - setVideoBufferBlankPages(); - column = 5; - row = 6; - - // Mocking out the vga calls - vga.initTest(); - defer vga.freeTest(); - - vga.addRepeatFunction("entry", vga.orig_entry); - vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); - - // Pre testing - defaultAllTesting(0, 6, 5); - - // Call function - displayPageNumber(); - - // Post test - defaultVariablesTesting(0, 6, 5); - - const text = "Page 0 of 4"; - - // Test both video and pages for page number 0 - var i: u32 = 0; - while (i < VIDEO_BUFFER_SIZE) : (i += 1) { - const b = video_buffer[i]; - if (i < START_OF_DISPLAYABLE_REGION - 11) { - expectEqual(blank, b); - } else if (i < START_OF_DISPLAYABLE_REGION) { - expectEqual(vga.orig_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(TtyError.OutOfBounds, putEntryAt('A', 100, 100)); - - // Post test - defaultAllTesting(0, 0, 0); - - // Tear down - resetGlobals(); -} - -test "putEntryAt not in displayable region" { - // Set up - setVideoBufferBlankPages(); - - // Mocking out the vga calls - vga.initTest(); - defer vga.freeTest(); - - vga.addRepeatFunction("entry", vga.orig_entry); - vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); - - // Enable and update cursor is only called once, can can use the consume function call - //vga.addConsumeFunction("enableCursor", vga.mock_enableCursor); - - // Pre testing - defaultAllTesting(0, 0, 0); - - // Call function - const x = 0; - const y = 0; - const char = 'A'; - try putEntryAt(char, x, y); - - // Post test - defaultVariablesTesting(0, 0, 0); - blankPagesTesting(); - - var i: u32 = 0; - while (i < VIDEO_BUFFER_SIZE) : (i += 1) { - const b = video_buffer[i]; - if (i == y * vga.WIDTH + x) { - expectEqual(vga.orig_entry(char, test_colour), b); - } else { - expectEqual(vga.orig_entry(0, test_colour), b); - } - } - - // Tear down - resetGlobals(); -} - -test "putEntryAt in displayable region page_index is 0" { - // Set up - setVideoBufferBlankPages(); - - // Mocking out the vga calls - vga.initTest(); - defer vga.freeTest(); - - vga.addRepeatFunction("entry", vga.orig_entry); - vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); - - // Pre testing - defaultAllTesting(0, 0, 0); - - // Call function - const x = 0; - const y = ROW_MIN; - const char = 'A'; - try putEntryAt(char, x, y); - - // 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.orig_entry(char, test_colour), c); - } else { - expectEqual(blank, c); - } - } - } - - var i: u32 = 0; - while (i < VIDEO_BUFFER_SIZE) : (i += 1) { - const b = video_buffer[i]; - if (i == y * vga.WIDTH + x) { - expectEqual(vga.orig_entry(char, test_colour), b); - } else { - expectEqual(vga.orig_entry(0, test_colour), b); - } - } - - // Tear down - resetGlobals(); -} - -test "putEntryAt in displayable region page_index is not 0" { - // Set up - // Mocking out the vga calls - vga.initTest(); - defer vga.freeTest(); - - vga.addRepeatFunction("entry", vga.orig_entry); - vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); - - // Enable and update cursor is only called once, can can use the consume function call - vga.addConsumeFunction("enableCursor", vga.mock_enableCursor); - - setVideoBufferBlankPages(); - - // Fill the 1'nd page (index 1) will all 1's - const ones = vga.orig_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(@as(u16, 0), char); - } - } - } - - // Call function - const x = 0; - const y = ROW_MIN; - const char = 'A'; - try putEntryAt(char, x, y); - - // Post test - defaultVariablesTesting(0, 0, 0); - - const text = "Page 0 of 4"; - - for (pages) |page, i| { - for (page) |c, j| { - if (i == 0 and j == 0) { - expectEqual(vga.orig_entry(char, test_colour), c); - } else if (i == 0) { - expectEqual(ones, c); - } else { - expectEqual(@as(u16, 0), c); - } - } - } - - // The top 7 rows won't be copied - var i: u32 = 0; - while (i < VIDEO_BUFFER_SIZE) : (i += 1) { - const b = video_buffer[i]; - if (i < START_OF_DISPLAYABLE_REGION - 11) { - expectEqual(blank, b); - } else if (i < START_OF_DISPLAYABLE_REGION) { - expectEqual(vga.orig_entry(text[i + 11 - START_OF_DISPLAYABLE_REGION], colour), b); - } else if (i == y * vga.WIDTH + x) { - expectEqual(vga.orig_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 = ROW_TOTAL + 1; - expectError(TtyError.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 = 0; - try pagesMoveRowsUp(rows_to_move); - - // 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 = 1; - try pagesMoveRowsUp(rows_to_move); - - // Post test - defaultVariablesTesting(0, 0, 0); - defaultVideoBufferTesting(); - - const to_add = rows_to_move * vga.WIDTH; - for (pages) |page, i| { - for (page) |c, j| { - if (j >= TOTAL_CHAR_ON_PAGE - to_add) { - if (i == 0) { - // The last rows will be blanks - expectEqual(blank, c); - } else { - expectEqual((i - 1) * TOTAL_CHAR_ON_PAGE + (j + to_add - TOTAL_CHAR_ON_PAGE), c); - } - } else { - // All rows moved up one, so add vga.WIDTH - expectEqual(i * TOTAL_CHAR_ON_PAGE + j + to_add, c); - } - } - } - - // 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 = ROW_TOTAL - 1; - try pagesMoveRowsUp(rows_to_move); - - // Post test - defaultVariablesTesting(0, 0, 0); - defaultVideoBufferTesting(); - - const to_add = rows_to_move * vga.WIDTH; - for (pages) |page, i| { - for (page) |c, j| { - if (j >= TOTAL_CHAR_ON_PAGE - to_add) { - if (i == 0) { - // The last rows will be blanks - expectEqual(blank, c); - } else { - expectEqual((i - 1) * TOTAL_CHAR_ON_PAGE + (j + to_add - TOTAL_CHAR_ON_PAGE), c); - } - } else { - // All rows moved up one, so add vga.WIDTH - expectEqual(i * TOTAL_CHAR_ON_PAGE + j + to_add, c); - } - } - } - - // 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 = ROW_TOTAL; - try pagesMoveRowsUp(rows_to_move); - - // Post test - defaultVariablesTesting(0, 0, 0); - defaultVideoBufferTesting(); - - for (pages) |page, i| { - for (page) |c, j| { - if (i == 0) { - // The last rows will be blanks - expectEqual(blank, c); - } else { - expectEqual((i - 1) * TOTAL_CHAR_ON_PAGE + j, c); - } - } - } - - // 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 = 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); - - const to_add = (row_test - vga.HEIGHT + 1) * vga.WIDTH; - for (pages) |page, i| { - for (page) |c, j| { - if (j >= TOTAL_CHAR_ON_PAGE - to_add) { - if (i == 0) { - // The last rows will be blanks - expectEqual(blank, c); - } else { - expectEqual((i - 1) * TOTAL_CHAR_ON_PAGE + (j + to_add - TOTAL_CHAR_ON_PAGE), c); - } - } else { - // All rows moved up one, so add vga.WIDTH - expectEqual(i * TOTAL_CHAR_ON_PAGE + j + to_add, c); - } - } - } - - var i: u32 = 0; - while (i < VIDEO_BUFFER_SIZE) : (i += 1) { - const buf = video_buffer[i]; - if (i < START_OF_DISPLAYABLE_REGION) { - expectEqual(i, buf); - } else if (i >= VIDEO_BUFFER_SIZE - to_add) { - expectEqual(blank, buf); - } else { - expectEqual(i + to_add, buf); - } - } - - // Tear down - resetGlobals(); -} - -test "scroll row is more than height" { - // Set up - setVideoBufferIncrementingBlankPages(); - setPagesIncrementing(); - - const row_test = 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); - - const to_add = (row_test - vga.HEIGHT + 1) * vga.WIDTH; - for (pages) |page, i| { - for (page) |c, j| { - if (j >= TOTAL_CHAR_ON_PAGE - to_add) { - if (i == 0) { - // The last rows will be blanks - expectEqual(blank, c); - } else { - expectEqual((i - 1) * TOTAL_CHAR_ON_PAGE + (j + to_add - TOTAL_CHAR_ON_PAGE), c); - } - } else { - // All rows moved up one, so add vga.WIDTH - expectEqual(i * TOTAL_CHAR_ON_PAGE + j + to_add, c); - } - } - } - - var i: u32 = 0; - while (i < VIDEO_BUFFER_SIZE) : (i += 1) { - const buf = video_buffer[i]; - if (i < START_OF_DISPLAYABLE_REGION) { - expectEqual(i, buf); - } else if (i >= VIDEO_BUFFER_SIZE - to_add) { - expectEqual(blank, buf); - } else { - expectEqual(i + to_add, buf); - } - } - - // Tear down - resetGlobals(); -} - -test "putChar new line within screen" { - // Set up - setVideoBufferBlankPages(); - - // Pre testing - column = 5; - row = 5; - defaultAllTesting(0, 5, 5); - - // Call function - try putChar('\n'); - - // 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 - try putChar('\n'); - - // 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 - try putChar('\t'); - - // 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 - try putChar('\t'); - - // 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 - try putChar('\t'); - - // 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 - try putChar('\r'); - - // 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 - try putChar('\x08'); - - // 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 - try putChar('\x08'); - - // 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 - try putChar('\x08'); - - // Post test - defaultAllTesting(0, 0, vga.WIDTH - 1); - - // Tear down - resetGlobals(); -} - -test "putChar any char in row" { - // Set up - setVideoBufferBlankPages(); - - // Mocking out the vga calls - vga.initTest(); - defer vga.freeTest(); - - vga.addRepeatFunction("entry", vga.orig_entry); - - // Pre testing - defaultAllTesting(0, 0, 0); - - // Call function - try putChar('A'); - - // Post test - defaultVariablesTesting(0, 0, 1); - blankPagesTesting(); - - var i: u32 = 0; - while (i < VIDEO_BUFFER_SIZE) : (i += 1) { - const buf = video_buffer[i]; - if (i == 0) { - expectEqual(vga.orig_entry('A', colour), buf); - } else { - expectEqual(blank, buf); - } - } - - // Tear down - resetGlobals(); -} - -test "putChar any char end of row" { - // Set up - setVideoBufferBlankPages(); - - // Mocking out the vga calls - vga.initTest(); - defer vga.freeTest(); - - vga.addRepeatFunction("entry", vga.orig_entry); - - // Pre testing - column = vga.WIDTH - 1; - defaultAllTesting(0, 0, vga.WIDTH - 1); - - // Call function - try putChar('A'); - - // Post test - defaultVariablesTesting(0, 1, 0); - blankPagesTesting(); - - var i: u32 = 0; - while (i < VIDEO_BUFFER_SIZE) : (i += 1) { - const buf = video_buffer[i]; - if (i == vga.WIDTH - 1) { - expectEqual(vga.orig_entry('A', colour), buf); - } else { - expectEqual(blank, buf); - } - } - - // Tear down - resetGlobals(); -} - -test "putChar any char end of screen" { - // Set up - setVideoBufferBlankPages(); - - // Mocking out the vga calls - vga.initTest(); - defer vga.freeTest(); - - vga.addRepeatFunction("entry", vga.orig_entry); - - // Pre testing - row = vga.HEIGHT - 1; - column = vga.WIDTH - 1; - defaultAllTesting(0, vga.HEIGHT - 1, vga.WIDTH - 1); - - // Call function - try putChar('A'); - - // 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.orig_entry('A', colour), c); - } else { - expectEqual(blank, c); - } - } - } - - var i: u32 = 0; - while (i < VIDEO_BUFFER_SIZE) : (i += 1) { - const buf = video_buffer[i]; - if (i == VIDEO_BUFFER_SIZE - vga.WIDTH - 1) { - expectEqual(vga.orig_entry('A', colour), buf); - } else { - expectEqual(blank, buf); - } - } - - // Tear down - resetGlobals(); -} - -test "printLogo" { - // Set up - setVideoBufferBlankPages(); - - // Mocking out the vga calls - vga.initTest(); - defer vga.freeTest(); - - vga.addRepeatFunction("entry", vga.orig_entry); - vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); - - // Pre testing - column = 0; - row = ROW_MIN; - - defaultAllTesting(0, ROW_MIN, 0); - - // Call function - printLogo(); - - // Post test - defaultVariablesTesting(0, ROW_MIN, 0); - blankPagesTesting(); - - var i: u32 = 0; - while (i < VIDEO_BUFFER_SIZE) : (i += 1) { - const buf = video_buffer[i]; - if (i < 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, buf); - } - } - - // 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(); - - // Mocking out the vga calls - vga.initTest(); - defer vga.freeTest(); - - vga.addRepeatFunction("entry", vga.orig_entry); - vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); - - vga.addConsumeFunction("disableCursor", vga.mock_disableCursor); - - // Pre testing - defaultVariablesTesting(0, 0, 0); - incrementingPagesTesting(); - defaultVideoBufferTesting(); - - // Call function - pageUp(); - - // Post test - defaultVariablesTesting(1, 0, 0); - incrementingPagesTesting(); - - const text = "Page 1 of 4"; - - var i: u32 = 0; - while (i < VIDEO_BUFFER_SIZE) : (i += 1) { - const b = video_buffer[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.orig_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(); - - // Mocking out the vga calls - vga.initTest(); - defer vga.freeTest(); - - vga.addRepeatFunction("entry", vga.orig_entry); - vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); - - vga.addConsumeFunction("disableCursor", vga.mock_disableCursor); - - // 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 = "Page 3 of 4"; - - var i: u32 = 0; - while (i < VIDEO_BUFFER_SIZE) : (i += 1) { - const b = video_buffer[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.orig_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" { - // Set up - setVideoBufferIncrementingBlankPages(); - setPagesIncrementing(); - - // Mocking out the vga calls - vga.initTest(); - defer vga.freeTest(); - - vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); - - // Pre testing - defaultVariablesTesting(0, 0, 0); - incrementingVideoBufferTesting(); - incrementingPagesTesting(); - - // Call function - clearScreen(); - - // Post test - defaultVariablesTesting(0, ROW_MIN, 0); - var i: u32 = 0; - while (i < VIDEO_BUFFER_SIZE) : (i += 1) { - const buf = video_buffer[i]; - if (i < START_OF_DISPLAYABLE_REGION) { - expectEqual(i, buf); - } else { - expectEqual(blank, buf); - } - } - - for (pages) |page, j| { - for (page) |c, k| { - if (j == 0) { - // The last rows will be blanks - expectEqual(blank, c); - } else { - expectEqual((j - 1) * TOTAL_CHAR_ON_PAGE + k, c); - } - } - } - - // Tear down - resetGlobals(); -} - -test "moveCursorLeft top left of screen" { - // Set up - setVideoBufferBlankPages(); - - // Mocking out the vga calls - vga.initTest(); - defer vga.freeTest(); - - vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); - - // 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(); - - // Mocking out the vga calls - vga.initTest(); - defer vga.freeTest(); - - vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); - - // 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(); - - // Mocking out the vga calls - vga.initTest(); - defer vga.freeTest(); - - vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); - - // 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(); - - // Mocking out the vga calls - vga.initTest(); - defer vga.freeTest(); - - vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); - - // 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(); - - // Mocking out the vga calls - vga.initTest(); - defer vga.freeTest(); - - vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); - - // 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(); - - // Mocking out the vga calls - vga.initTest(); - defer vga.freeTest(); - - vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); - - // 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" { - // Set up - // Mocking out the vga calls - vga.initTest(); - defer vga.freeTest(); - - vga.addConsumeFunction("entry", vga.orig_entry); - - // Pre testing - - // Call function - const new_colour = vga.orig_entryColour(vga.COLOUR_WHITE, vga.COLOUR_WHITE); - setColour(new_colour); - - // Post test - expectEqual(new_colour, colour); - expectEqual(vga.orig_entry(0, new_colour), blank); - - // Tear down - resetGlobals(); -} - -test "writeString" { - // Set up - setVideoBufferBlankPages(); - - // Mocking out the vga calls - vga.initTest(); - defer vga.freeTest(); - - vga.addRepeatFunction("entry", vga.orig_entry); - - vga.addConsumeFunction("updateCursor", vga.mock_updateCursor); - - // Pre testing - row = ROW_MIN; - defaultAllTesting(0, ROW_MIN, 0); - - // Call function - try writeString("ABC"); - - // Post test - defaultVariablesTesting(0, ROW_MIN, 3); - for (pages) |page, i| { - for (page) |c, j| { - if ((i == 0) and (j == 0)) { - expectEqual(vga.orig_entry('A', colour), c); - } else if ((i == 0) and (j == 1)) { - expectEqual(vga.orig_entry('B', colour), c); - } else if ((i == 0) and (j == 2)) { - expectEqual(vga.orig_entry('C', colour), c); - } else { - expectEqual(blank, c); - } - } - } - - var i: u32 = 0; - while (i < VIDEO_BUFFER_SIZE) : (i += 1) { - const buf = video_buffer[i]; - if (i == START_OF_DISPLAYABLE_REGION) { - expectEqual(vga.orig_entry('A', colour), buf); - } else if (i == START_OF_DISPLAYABLE_REGION + 1) { - expectEqual(vga.orig_entry('B', colour), buf); - } else if (i == START_OF_DISPLAYABLE_REGION + 2) { - expectEqual(vga.orig_entry('C', colour), buf); - } else { - expectEqual(blank, buf); - } - } - - // Tear down - resetGlobals(); -} - -test "init 0,0" { - // Set up - setVideoBufferBlankPages(); - - // Mocking out the vga.updateCursor call for updating the hardware cursor - vga.initTest(); - defer vga.freeTest(); - - vga.addTestParams("getCursor", .{@as(u16, 0)}); - - vga.addRepeatFunction("entryColour", vga.orig_entryColour); - vga.addRepeatFunction("entry", vga.orig_entry); - vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); - - vga.addConsumeFunction("enableCursor", vga.mock_enableCursor); - - // Pre testing - defaultAllTesting(0, 0, 0); - - // Call function - init(); - - // Post test - defaultVariablesTesting(0, ROW_MIN, 0); - blankPagesTesting(); - - var i: u32 = 0; - while (i < VIDEO_BUFFER_SIZE) : (i += 1) { - const buf = video_buffer[i]; - if (i < 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, buf); - } - } - - // Tear down - resetGlobals(); -} - -test "init not 0,0" { - // Set up - setVideoBufferBlankPages(); - - // Mocking out the vga.updateCursor call for updating the hardware cursor - vga.initTest(); - defer vga.freeTest(); - - vga.addTestParams("getCursor", .{vga.WIDTH}); - - vga.addRepeatFunction("entryColour", vga.orig_entryColour); - vga.addRepeatFunction("entry", vga.orig_entry); - vga.addRepeatFunction("updateCursor", vga.mock_updateCursor); - - vga.addConsumeFunction("enableCursor", vga.mock_enableCursor); - - // Pre testing - defaultAllTesting(0, 0, 0); - - // Call function - init(); - - // Post test - defaultVariablesTesting(0, ROW_MIN + 1, 0); - blankPagesTesting(); - - var i: u32 = 0; - while (i < VIDEO_BUFFER_SIZE) : (i += 1) { - const buf = video_buffer[i]; - if (i < 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, buf); - } - } - - // Tear down - resetGlobals(); -} - -/// -/// Test the init function set up everything properly. -/// -fn rt_initialisedGlobals() void { - if (@ptrToInt(video_buffer.ptr) != @ptrToInt(&KERNEL_ADDR_OFFSET) + 0xB8000) { - panic(@errorReturnTrace(), "Video buffer not at correct virtual address, found: {}\n", .{@ptrToInt(video_buffer.ptr)}); - } - - if (page_index != 0) { - panic(@errorReturnTrace(), "Page index not at zero, found: {}\n", .{page_index}); - } - - if (colour != vga.entryColour(vga.COLOUR_LIGHT_GREY, vga.COLOUR_BLACK)) { - panic(@errorReturnTrace(), "Colour not set up properly, found: {}\n", .{colour}); - } - - if (blank != vga.entry(0, colour)) { - panic(@errorReturnTrace(), "Blank not set up properly, found: {}\n", .{blank}); - } - - // Make sure the screen isn't all blank - var all_blank = true; - var i: u32 = 0; - while (i < VIDEO_BUFFER_SIZE) : (i += 1) { - const buf = video_buffer[i]; - if (buf != blank and buf != 0) { - all_blank = false; - break; - } - } - - if (all_blank) { - panic(@errorReturnTrace(), "Screen all blank, should have logo and page number\n", .{}); - } - - log.logInfo("TTY: Tested globals\n", .{}); -} - -/// -/// Test printing a string will output to the screen. This will check both the video memory and -/// the pages. -/// -fn rt_printString() void { - const text = "abcdefg"; - const clear_text = "\x08" ** text.len; - - print(text, .{}); - - // Check the video memory - var counter: u32 = 0; - var i: u32 = 0; - while (i < VIDEO_BUFFER_SIZE) : (i += 1) { - const buf = video_buffer[i]; - if (counter < text.len and buf == vga.entry(text[counter], colour)) { - counter += 1; - } else if (counter == text.len) { - // Found all the text - break; - } else { - counter = 0; - } - } - - if (counter != text.len) { - panic(@errorReturnTrace(), "Didn't find the printed text in video memory\n", .{}); - } - - // Check the pages - counter = 0; - for (pages[0]) |c| { - if (counter < text.len and c == vga.entry(text[counter], colour)) { - counter += 1; - } else if (counter == text.len) { - // Found all the text - break; - } else { - counter = 0; - } - } - - if (counter != text.len) { - panic(@errorReturnTrace(), "Didn't find the printed text in pages\n", .{}); - } - - // Clear the text - print(clear_text, .{}); - - log.logInfo("TTY: Tested printing\n", .{}); -} - -/// -/// Run all the runtime tests. -/// -fn runtimeTests() void { - rt_initialisedGlobals(); - rt_printString(); + tty = arch.initTTY(boot_payload); + allocator = alloc; } diff --git a/test/kernel/arch/x86/rt-test.py b/test/kernel/arch/x86/rt-test.py index f972d6c..eb82bf9 100644 --- a/test/kernel/arch/x86/rt-test.py +++ b/test/kernel/arch/x86/rt-test.py @@ -36,4 +36,9 @@ def get_test_cases(TestCase): TestCase("Syscalls init", [r"Init syscalls"]), TestCase("Syscalls tests", [r"Syscalls: Tested no args", r"Syscalls: Tested 1 arg", r"Syscalls: Tested 2 args", r"Syscalls: Tested 3 args", r"Syscalls: Tested 4 args", r"Syscalls: Tested 5 args"]), TestCase("Syscalls done", [r"Done syscalls"]), + TestCase("VGA init", [r"Init vga"]), + TestCase("VGA tests", [r"VGA: Tested max scan line", r"VGA: Tested cursor shape", r"VGA: Tested updating cursor"]), + TestCase("VGA done", [r"Done vga"]), + TestCase("TTY tests", [r"TTY: Tested globals", r"TTY: Tested printing"]), + ] diff --git a/test/mock/kernel/arch_mock.zig b/test/mock/kernel/arch_mock.zig index 2cafebf..554ef9b 100644 --- a/test/mock/kernel/arch_mock.zig +++ b/test/mock/kernel/arch_mock.zig @@ -7,6 +7,7 @@ const idt = @import("idt_mock.zig"); const vmm = @import("vmm_mock.zig"); const paging = @import("paging_mock.zig"); const Serial = @import("../../../src/kernel/serial.zig").Serial; +const TTY = @import("../../../src/kernel/tty.zig").TTY; const mock_framework = @import("mock_framework.zig"); pub const initTest = mock_framework.initTest; @@ -106,6 +107,16 @@ pub fn initSerial(boot_payload: BootPayload) Serial { return .{ .write = undefined }; } +pub fn initTTY(boot_payload: BootPayload) TTY { + return .{ + .print = undefined, + .setCursor = undefined, + .cols = undefined, + .rows = undefined, + .clear = null, + }; +} + pub fn initMem(payload: BootPayload) std.mem.Allocator.Error!mem.MemProfile { return MemProfile{ .vaddr_end = @ptrCast([*]u8, &KERNEL_VADDR_END), diff --git a/test/rt-test.py b/test/rt-test.py index 1750c63..a79231a 100644 --- a/test/rt-test.py +++ b/test/rt-test.py @@ -56,16 +56,12 @@ def get_pre_archinit_cases(): def get_post_archinit_cases(): return [ TestCase("Arch init finishes", [r"Arch init done"]), - TestCase("VGA init", [r"Init vga"]), - TestCase("VGA tests", [r"VGA: Tested max scan line", r"VGA: Tested cursor shape", r"VGA: Tested updating cursor"]), - TestCase("VGA done", [r"Done vga"]), - - TestCase("TTY init", [r"Init tty"]), - TestCase("TTY tests", [r"TTY: Tested globals", r"TTY: Tested printing"]), - TestCase("TTY done", [r"Done tty"]), TestCase("Heap", [r"Init heap", r"Done heap"]), + TestCase("TTY init", [r"Init tty"]), + TestCase("TTY done", [r"Done tty"]), + TestCase("Init finishes", [r"Init done"]), TestCase("Panic tests", [r"Kernel panic: integer overflow", r"c[a-z\d]+: panic", r"c[a-z\d]+: panic.runtimeTests", r"c[a-z\d]+: kmain", r"c[a-z\d]+: start_higher_half"], r"\[ERROR\] ")