diff --git a/build/build.cmd b/build/build.cmd
index 86f92bc..7fa1db2 100644
--- a/build/build.cmd
+++ b/build/build.cmd
@@ -13,7 +13,7 @@ rmdir /S /Q ..\bin >nul 2>&1
mkdir ..\bin
cd ..\bin
-csc /debug:embedded /noconfig /nostdlib /runtimemetadataversion:v4.0.30319 ../src/PatienceOS.Kernel/kernel.cs ../src/PatienceOS.Kernel/Console.cs ../src/PatienceOS.Kernel/FrameBuffer.cs ../src/PatienceOS.Kernel/zerosharp.cs /out:kernel.ilexe /langversion:latest /unsafe || goto Error
+csc /debug:embedded /noconfig /nostdlib /runtimemetadataversion:v4.0.30319 ../src/PatienceOS.Kernel/Color.cs ../src/PatienceOS.Kernel/Console.cs ../src/PatienceOS.Kernel/FrameBuffer.cs ../src/PatienceOS.Kernel/kernel.cs ../src/PatienceOS.Kernel/zerosharp.cs /out:kernel.ilexe /langversion:latest /unsafe || goto Error
ilc --targetos windows --targetarch x86 --instruction-set base --verbose kernel.ilexe -g -o kernel.obj --systemmodule kernel --map kernel.map -O || goto Error
nasm -f win32 -o loader.obj ../src/loader.asm || goto Error
@@ -23,7 +23,8 @@ nasm -f win32 -o loader.obj ../src/loader.asm || goto Error
ld -m i386pe -T ../src/linker.ld -o kernel.bin loader.obj kernel.obj || goto Error
objcopy -O elf32-i386 kernel.bin kernel.elf || goto Error
-qemu-system-i386 -cpu pentium3 -kernel kernel.elf -d int -no-reboot -no-shutdown || goto Error
+qemu-system-i386 -cpu pentium3 -kernel kernel.elf -no-reboot -no-shutdown || goto Error
+:: qemu-system-i386 -cpu pentium3 -kernel kernel.elf -d int -no-reboot -no-shutdown || goto Error
cd ..\build
exit /B
diff --git a/src/PatienceOS.Kernel.Tests/ConsoleTests.cs b/src/PatienceOS.Kernel.Tests/ConsoleTests.cs
index 5489012..81d9c33 100644
--- a/src/PatienceOS.Kernel.Tests/ConsoleTests.cs
+++ b/src/PatienceOS.Kernel.Tests/ConsoleTests.cs
@@ -73,7 +73,7 @@ public void Should_Clear_A_EOL_B()
unsafe public class ThreeXThree
{
[Fact]
- public void Should_Write_ABC_EOL_DEF_EOL_GHI()
+ public void Should_Write_ABC_DEF_GHI()
{
// Given
byte* buffer = stackalloc byte[3 * 3 * 2];
@@ -94,12 +94,143 @@ public void Should_Write_ABC_EOL_DEF_EOL_GHI()
Assert.Equal((byte)'H', buffer[14]);
Assert.Equal((byte)'I', buffer[16]);
}
+
+ [Fact]
+ public void Should_Clear_ABC_DEF_GHI()
+ {
+ // Given
+ byte* buffer = stackalloc byte[3 * 3 * 2];
+ var frameBuffer = new FrameBuffer(buffer);
+ var console = new Console(3, 3, frameBuffer);
+
+ // When
+ console.Print("ABCDEFGHI");
+ console.Clear();
+
+ // Then
+ Assert.Equal((byte)' ', buffer[0]);
+ Assert.Equal((byte)' ', buffer[2]);
+ Assert.Equal((byte)' ', buffer[4]);
+ Assert.Equal((byte)' ', buffer[6]);
+ Assert.Equal((byte)' ', buffer[8]);
+ Assert.Equal((byte)' ', buffer[10]);
+ Assert.Equal((byte)' ', buffer[12]);
+ Assert.Equal((byte)' ', buffer[14]);
+ Assert.Equal((byte)' ', buffer[16]);
+ }
+
+ [Fact]
+ public void Should_Scroll_One_Line()
+ {
+ // Given
+ byte* buffer = stackalloc byte[3 * 3 * 2];
+ var frameBuffer = new FrameBuffer(buffer);
+ var console = new Console(3, 3, frameBuffer);
+
+ // When
+ console.Print("AAA");
+ console.Print("BBB");
+ console.Print("CCC");
+ console.Print("DDD");
+
+ // Then
+ Assert.Equal((byte)'B', buffer[0]);
+ Assert.Equal((byte)'B', buffer[2]);
+ Assert.Equal((byte)'B', buffer[4]);
+ Assert.Equal((byte)'C', buffer[6]);
+ Assert.Equal((byte)'C', buffer[8]);
+ Assert.Equal((byte)'C', buffer[10]);
+ Assert.Equal((byte)'D', buffer[12]);
+ Assert.Equal((byte)'D', buffer[14]);
+ Assert.Equal((byte)'D', buffer[16]);
+ }
+
+ [Fact]
+ public void Should_Scroll_Two_Lines()
+ {
+ // Given
+ byte* buffer = stackalloc byte[3 * 3 * 2];
+ var frameBuffer = new FrameBuffer(buffer);
+ var console = new Console(3, 3, frameBuffer);
+
+ // When
+ console.Print("AAA");
+ console.Print("BBB");
+ console.Print("CCC");
+ console.Print("DDD");
+ console.Print("EEE");
+
+ // Then
+ Assert.Equal((byte)'C', buffer[0]);
+ Assert.Equal((byte)'C', buffer[2]);
+ Assert.Equal((byte)'C', buffer[4]);
+ Assert.Equal((byte)'D', buffer[6]);
+ Assert.Equal((byte)'D', buffer[8]);
+ Assert.Equal((byte)'D', buffer[10]);
+ Assert.Equal((byte)'E', buffer[12]);
+ Assert.Equal((byte)'E', buffer[14]);
+ Assert.Equal((byte)'E', buffer[16]);
+ }
+
+ [Fact]
+ public void Should_Scroll_Three_Lines()
+ {
+ // Given
+ byte* buffer = stackalloc byte[3 * 3 * 2];
+ var frameBuffer = new FrameBuffer(buffer);
+ var console = new Console(3, 3, frameBuffer);
+
+ // When
+ console.Print("AAA");
+ console.Print("BBB");
+ console.Print("CCC");
+ console.Print("DDD");
+ console.Print("EEE");
+ console.Print("FFF");
+
+ // Then
+ Assert.Equal((byte)'D', buffer[0]);
+ Assert.Equal((byte)'D', buffer[2]);
+ Assert.Equal((byte)'D', buffer[4]);
+ Assert.Equal((byte)'E', buffer[6]);
+ Assert.Equal((byte)'E', buffer[8]);
+ Assert.Equal((byte)'E', buffer[10]);
+ Assert.Equal((byte)'F', buffer[12]);
+ Assert.Equal((byte)'F', buffer[14]);
+ Assert.Equal((byte)'F', buffer[16]);
+ }
+
+ [Fact]
+ public void Should_Scroll_And_Blank_Last_Line()
+ {
+ // Given
+ byte* buffer = stackalloc byte[3 * 3 * 2];
+ var frameBuffer = new FrameBuffer(buffer);
+ var console = new Console(3, 3, frameBuffer);
+
+ // When
+ console.Print("AAA");
+ console.Print("BBB");
+ console.Print("CCC");
+ console.Print("D");
+
+ // Then
+ Assert.Equal((byte)'B', buffer[0]);
+ Assert.Equal((byte)'B', buffer[2]);
+ Assert.Equal((byte)'B', buffer[4]);
+ Assert.Equal((byte)'C', buffer[6]);
+ Assert.Equal((byte)'C', buffer[8]);
+ Assert.Equal((byte)'C', buffer[10]);
+ Assert.Equal((byte)'D', buffer[12]);
+ Assert.Equal((byte)' ', buffer[14]);
+ Assert.Equal((byte)' ', buffer[16]);
+ }
}
unsafe public class HelloWorld
{
[Fact]
- public void Should_Write_Hello_Space_World_With_Print_Statement()
+ public void Should_Write_Hello_World_With_Print_Statement()
{
// Given
byte* buffer = stackalloc byte[80 * 25 * 2];
@@ -124,7 +255,7 @@ public void Should_Write_Hello_Space_World_With_Print_Statement()
}
[Fact]
- public void Should_Write_Hello_Space_World_With_Print_Statements()
+ public void Should_Write_Hello_World_With_Print_Statements()
{
// Given
byte* buffer = stackalloc byte[80 * 25 * 2];
@@ -151,7 +282,7 @@ public void Should_Write_Hello_Space_World_With_Print_Statements()
}
[Fact]
- public void Should_Write_Hello_EOL_World_EOL_With_Print_Statement()
+ public void Should_Write_Hello_CRLF_World_With_Print_Statement()
{
// Given
byte* buffer = stackalloc byte[80 * 25 * 2];
@@ -175,7 +306,7 @@ public void Should_Write_Hello_EOL_World_EOL_With_Print_Statement()
}
[Fact]
- public void Should_Write_Hello_EOL_World_EOL_With_Print_Statements()
+ public void Should_Write_Hello_CRLF_World_With_Print_Statements()
{
// Given
byte* buffer = stackalloc byte[80 * 25 * 2];
@@ -200,7 +331,7 @@ public void Should_Write_Hello_EOL_World_EOL_With_Print_Statements()
}
[Fact]
- public void Should_Write_Hello_EOL_World_EOL_With_PrintLine_Statements()
+ public void Should_Write_Hello_CRLF_World_With_PrintLine_Statements()
{
// Given
byte* buffer = stackalloc byte[80 * 25 * 2];
diff --git a/src/PatienceOS.Kernel.Tests/FrameBufferTests.cs b/src/PatienceOS.Kernel.Tests/FrameBufferTests.cs
index dea9620..55125fd 100644
--- a/src/PatienceOS.Kernel.Tests/FrameBufferTests.cs
+++ b/src/PatienceOS.Kernel.Tests/FrameBufferTests.cs
@@ -25,5 +25,29 @@ public void FrameBuffer_Should_Contain_Hello()
Assert.Equal((byte)'l', buffer[3]);
Assert.Equal((byte)'o', buffer[4]);
}
+
+ [Fact]
+ public void FrameBuffer_Should_Copy()
+ {
+ // Given
+ byte* buffer = stackalloc byte[5];
+ var frameBuffer = new FrameBuffer(buffer);
+
+ frameBuffer.Write(0, (byte)'H');
+ frameBuffer.Write(1, (byte)'e');
+ frameBuffer.Write(2, (byte)'l');
+ frameBuffer.Write(3, (byte)'l');
+ frameBuffer.Write(4, (byte)'o');
+
+ // When
+ frameBuffer.Copy(3, 0, 2);
+
+ // Then
+ Assert.Equal((byte)'l', buffer[0]);
+ Assert.Equal((byte)'o', buffer[1]);
+ Assert.Equal((byte)'l', buffer[2]);
+ Assert.Equal((byte)'l', buffer[3]);
+ Assert.Equal((byte)'o', buffer[4]);
+ }
}
}
\ No newline at end of file
diff --git a/src/PatienceOS.Kernel/Color.cs b/src/PatienceOS.Kernel/Color.cs
new file mode 100644
index 0000000..2e5f328
--- /dev/null
+++ b/src/PatienceOS.Kernel/Color.cs
@@ -0,0 +1,28 @@
+namespace PatienceOS.Kernel
+{
+ ///
+ /// Allowable terminal colors
+ ///
+ ///
+ /// List of colours sourced from here:
+ ///
+ public enum Color
+ {
+ Black = 0x00,
+ Blue = 0x01,
+ Green = 0x02,
+ Cyan = 0x03,
+ Red = 0x04,
+ Magenta = 0x05,
+ Brown = 0x06,
+ LightGrey = 0x07,
+ DarkGrey = 0x08,
+ LightBlue = 0x09,
+ LightGreen = 0x0A,
+ LightCyan = 0x0B,
+ LightRed = 0x0C,
+ LightMagenta = 0x0D,
+ Yellow = 0x0E,
+ White = 0x0F,
+ }
+}
diff --git a/src/PatienceOS.Kernel/Console.cs b/src/PatienceOS.Kernel/Console.cs
index e305cb6..bc4dec4 100644
--- a/src/PatienceOS.Kernel/Console.cs
+++ b/src/PatienceOS.Kernel/Console.cs
@@ -10,28 +10,44 @@ unsafe public struct Console
private FrameBuffer frameBuffer;
- private int column = 0; // nb. Incremented by two for each write, to account for the text colouring
+ private int column = 0;
private int row = 0;
- private byte foregroundColor = 0x0F; // White
+ private Color foregroundColor;
+
public Console(int width, int height, FrameBuffer frameBuffer)
{
this.width = width;
this.height = height;
+ this.foregroundColor = Color.White;
+ this.frameBuffer = frameBuffer;
+ }
+
+ public Console(int width, int height, Color foregroundColor, FrameBuffer frameBuffer)
+ {
+ this.width = width;
+ this.height = height;
+ this.foregroundColor = foregroundColor;
this.frameBuffer = frameBuffer;
}
+
///
/// Clear the screen
///
+ ///
+ /// Blanks the screen by writing the ASCII space character to each character cell
+ ///
public void Clear()
{
- for (int i = 0; i < width * height * 2; i += 2)
+ // Reset the cursor position
+ column = 0;
+ row = 0;
+
+ for (int i = 0; i < width * height; i++)
{
- // Write directly to the video memory
- frameBuffer.Write(i, (byte)' ');
- frameBuffer.Write(i + 1, foregroundColor);
+ Print(' ');
}
// Reset the cursor position
@@ -39,54 +55,91 @@ public void Clear()
row = 0;
}
+ ///
+ /// Print a string to the current cursor position
+ /// and then moves the cursor to the next line
+ ///
+ public void PrintLine(string s)
+ {
+ Print(s);
+ Print("\n");
+ }
+
///
/// Print a string to the current cursor position
///
- ///
- /// Assumes each screen character is represented by two bytes aligned as a 16-bit word,
- /// see
- ///
public void Print(string s)
{
fixed (char* ps = s)
{
for (int i = 0; i < s.Length; i++)
{
- // Perform a CRLF if we encounter a Newline character
- if (ps[i] == '\n')
- {
- column = 0;
- row++;
-
- continue;
- }
-
- // Perform a CRLF when the cursor reaches the end of the terminal line
- // eg.column is 0 to 79, width = 80
- if (column >= width * 2)
- {
- column = 0;
- row++;
- }
-
- // Write directly to the video memory
- frameBuffer.Write(row * width * 2 + column, (byte)ps[i]);
- frameBuffer.Write(row * width * 2 + column + 1, foregroundColor);
-
- // Move the cursor right by one
- column += 2;
+ Print(ps[i]);
}
}
}
///
- /// Prints a string to the current cursor position
- /// and then moves the cursor to the next line
+ /// Print a character to the current cursor position
///
- public void PrintLine(string s)
+ public void Print(char c)
{
- Print(s);
- Print("\n");
+ // Scroll if the cursor has dropped off the bottom of the terminal
+ if (row == height)
+ {
+ frameBuffer.Copy(width * 2, 0, (height - 1) * width * 2);
+
+ row--;
+
+ // Blank the last line ready for writing to
+ for (int i = 0; i < width; i++)
+ {
+ WriteVGATextCharacter(' ');
+
+ // Move the cursor right by one character
+ column++;
+ }
+
+ // Move the cursor to the beginning of the current line
+ column = 0;
+ }
+
+ // Perform a CRLF if we encounter a Newline character
+ if (c == '\n')
+ {
+ column = 0;
+ row++;
+
+ return;
+ }
+
+ WriteVGATextCharacter(c);
+
+ // Move the cursor right by one character
+ column++;
+
+ // Perform a CRLF when the cursor reaches the end of the terminal line
+ // eg.column is 0 to 79, width = 80
+ if (column == width)
+ {
+ column = 0;
+ row++;
+ }
+ }
+
+ ///
+ /// Write directly to the video memory, calculating the
+ /// positional index required for the linear framebuffer
+ ///
+ ///
+ /// Assumes each screen character is represented by two bytes aligned as a 16-bit word,
+ /// see
+ ///
+ private void WriteVGATextCharacter(char c)
+ {
+ frameBuffer.Write(row * width * 2 + column * 2, (byte)c);
+ frameBuffer.Write(row * width * 2 + column * 2 + 1, (byte)foregroundColor);
+ //TODO: background color
}
}
}
diff --git a/src/PatienceOS.Kernel/FrameBuffer.cs b/src/PatienceOS.Kernel/FrameBuffer.cs
index bee0396..2261776 100644
--- a/src/PatienceOS.Kernel/FrameBuffer.cs
+++ b/src/PatienceOS.Kernel/FrameBuffer.cs
@@ -1,4 +1,6 @@
-namespace PatienceOS.Kernel
+using System;
+
+namespace PatienceOS.Kernel
{
///
/// A linear framebuffer of bytes
@@ -16,5 +18,13 @@ public void Write(int position, byte value)
{
buffer[position] = value;
}
+
+ public void Copy(int sourcePosition, int destinationPosition, int length)
+ {
+ for (int i = 0; i < length; i++)
+ {
+ buffer[destinationPosition + i] = buffer[sourcePosition + i];
+ }
+ }
}
}
diff --git a/src/PatienceOS.Kernel/kernel.cs b/src/PatienceOS.Kernel/kernel.cs
index 9ebea6f..3b5a346 100644
--- a/src/PatienceOS.Kernel/kernel.cs
+++ b/src/PatienceOS.Kernel/kernel.cs
@@ -15,7 +15,7 @@ static int Main()
// https://migeel.sk/blog/2023/09/15/reverse-engineering-natively-compiled-dotnet-apps/
var frameBuffer = new FrameBuffer((byte*)VideoBaseAddress);
- var console = new Console(Width, Height, frameBuffer);
+ var console = new Console(Width, Height, Color.Green, frameBuffer);
console.Clear();