From f72eacc4fbd33d4091fa45e2bf81aae3159ac938 Mon Sep 17 00:00:00 2001 From: Achilleas Anagnostopoulos Date: Sun, 26 Mar 2017 09:34:53 +0100 Subject: [PATCH] Define VGA console The VGA console frame buffer is mapped to the physical address 0xB8000. --- kernel/driver/video/console/console.go | 4 +- kernel/driver/video/console/vga.go | 106 ++++++++++++ kernel/driver/video/console/vga_test.go | 204 ++++++++++++++++++++++++ 3 files changed, 312 insertions(+), 2 deletions(-) create mode 100644 kernel/driver/video/console/vga.go create mode 100644 kernel/driver/video/console/vga_test.go diff --git a/kernel/driver/video/console/console.go b/kernel/driver/video/console/console.go index b041fcc..8762d70 100644 --- a/kernel/driver/video/console/console.go +++ b/kernel/driver/video/console/console.go @@ -34,8 +34,8 @@ const ( Down ) -// The console.Interface is implemented by objects that can function as physical consoles. -type Interface interface { +// The Console interface is implemented by objects that can function as physical consoles. +type Console interface { sync.Locker // Dimensions returns the width and height of the console in characters. diff --git a/kernel/driver/video/console/vga.go b/kernel/driver/video/console/vga.go new file mode 100644 index 0000000..ca6e2d8 --- /dev/null +++ b/kernel/driver/video/console/vga.go @@ -0,0 +1,106 @@ +package console + +import ( + "reflect" + "unsafe" +) + +const ( + clearColor = Black + clearChar = byte(' ') +) + +// Vga implements an EGA-compatible text console. At the moment, it uses the +// ega console physical address as its outpucons. After implementing a memory +// allocator, each console will use its own framebuffer while the active console +// will periodically sync its internal buffer with the physical screen buffer. +type Vga struct { + width uint16 + height uint16 + + fb []uint16 +} + +// Init sets up the console. +func (cons *Vga) Init() { + cons.width = 80 + cons.height = 25 + + // Set up our frame buffer object by creating a fake slice object pointing + // to the physical address of the screen buffer. + if cons.fb != nil { + return + } + + cons.fb = *(*[]uint16)(unsafe.Pointer(&reflect.SliceHeader{ + Len: int(cons.width * cons.height), + Cap: int(cons.width * cons.height), + Data: uintptr(0xB8000), + })) +} + +// Clear clears the specified rectangular region +func (cons *Vga) Clear(x, y, width, height uint16) { + var ( + attr = uint16((clearColor << 4) | clearColor) + clr = attr | uint16(clearChar) + rowOffset, colOffset uint16 + ) + + // clip rectangle + if x >= cons.width { + x = cons.width + } + if y >= cons.height { + y = cons.height + } + + if x+width > cons.width { + width = cons.width - x + } + if y+height > cons.height { + height = cons.height - y + } + + rowOffset = (y * cons.width) + x + for ; height > 0; height, rowOffset = height-1, rowOffset+cons.width { + for colOffset = rowOffset; colOffset < rowOffset+width; colOffset++ { + cons.fb[colOffset] = clr + } + } +} + +// Dimensions returns the console width and height in characters. +func (cons *Vga) Dimensions() (uint16, uint16) { + return cons.width, cons.height +} + +// Scroll a particular number of lines to the specified direction. +func (cons *Vga) Scroll(dir ScrollDir, lines uint16) { + if lines == 0 || lines > cons.height { + return + } + + var i uint16 + offset := lines * cons.width + + switch dir { + case Up: + for ; i < (cons.height-lines)*cons.width; i++ { + cons.fb[i] = cons.fb[i+offset] + } + case Down: + for i = cons.height*cons.width - 1; i >= lines*cons.width; i-- { + cons.fb[i] = cons.fb[i-offset] + } + } +} + +// Write a char to the specified location. +func (cons *Vga) Write(ch byte, attr Attr, x, y uint16) { + if x >= cons.width || y >= cons.height { + return + } + + cons.fb[(y*cons.width)+x] = (uint16(attr) << 8) | uint16(ch) +} diff --git a/kernel/driver/video/console/vga_test.go b/kernel/driver/video/console/vga_test.go new file mode 100644 index 0000000..06bda9b --- /dev/null +++ b/kernel/driver/video/console/vga_test.go @@ -0,0 +1,204 @@ +package console + +import "testing" + +func TestVgaInit(t *testing.T) { + var cons Vga + cons.Init() + + var expWidth uint16 = 80 + var expHeight uint16 = 25 + + if w, h := cons.Dimensions(); w != expWidth || h != expHeight { + t.Fatalf("expected console dimensions after Init() to be (%d, %d); got (%d, %d)", expWidth, expHeight, w, h) + } +} + +func TestVgaClear(t *testing.T) { + specs := []struct { + // Input rect + x, y, w, h uint16 + + // Expected area to be cleared + expX, expY, expW, expH uint16 + }{ + { + 0, 0, 500, 500, + 0, 0, 80, 25, + }, + { + 10, 10, 11, 50, + 10, 10, 11, 15, + }, + { + 10, 10, 110, 1, + 10, 10, 70, 1, + }, + { + 70, 20, 20, 20, + 70, 20, 10, 5, + }, + { + 90, 25, 20, 20, + 0, 0, 0, 0, + }, + { + 12, 12, 5, 6, + 12, 12, 5, 6, + }, + } + + var cons = Vga{fb: make([]uint16, 80*25)} + cons.Init() + + testPat := uint16(0xDEAD) + clearPat := (uint16(clearColor) << 8) | uint16(clearChar) + +nextSpec: + for specIndex, spec := range specs { + // Fill FB with test pattern + for i := 0; i < len(cons.fb); i++ { + cons.fb[i] = testPat + } + + cons.Clear(spec.x, spec.y, spec.w, spec.h) + + var x, y uint16 + for y = 0; y < cons.height; y++ { + for x = 0; x < cons.width; x++ { + fbVal := cons.fb[(y*cons.width)+x] + + if x < spec.expX || y < spec.expY || x >= spec.expX+spec.expW || y >= spec.expY+spec.expH { + if fbVal != testPat { + t.Errorf("[spec %d] expected char at (%d, %d) not to be cleared", specIndex, x, y) + continue nextSpec + } + } else { + if fbVal != clearPat { + t.Errorf("[spec %d] expected char at (%d, %d) to be cleared", specIndex, x, y) + continue nextSpec + } + } + } + } + } +} + +func TestVgaScrollUp(t *testing.T) { + specs := []uint16{ + 0, + 1, + 2, + } + + var cons = Vga{fb: make([]uint16, 80*25)} + cons.Init() + +nextSpec: + for specIndex, lines := range specs { + // Fill buffer with test pattern + var x, y, index uint16 + for y = 0; y < cons.height; y++ { + for x = 0; x < cons.width; x++ { + cons.fb[index] = (y << 8) | x + index++ + } + } + + cons.Scroll(Up, lines) + + // Check that rows 1 to (height - lines) have been scrolled up + index = 0 + for y = 0; y < cons.height-lines; y++ { + for x = 0; x < cons.width; x++ { + expVal := ((y + lines) << 8) | x + if cons.fb[index] != expVal { + t.Errorf("[spec %d] expected value at (%d, %d) to be %d; got %d", specIndex, x, y, expVal, cons.fb[index]) + continue nextSpec + } + index++ + } + } + } +} + +func TestVgaScrollDown(t *testing.T) { + specs := []uint16{ + 0, + 1, + 2, + } + + var cons = Vga{fb: make([]uint16, 80*25)} + cons.Init() + +nextSpec: + for specIndex, lines := range specs { + // Fill buffer with test pattern + var x, y, index uint16 + for y = 0; y < cons.height; y++ { + for x = 0; x < cons.width; x++ { + cons.fb[index] = (y << 8) | x + index++ + } + } + + cons.Scroll(Down, lines) + + // Check that rows lines to height have been scrolled down + index = lines * cons.width + for y = lines; y < cons.height-lines; y++ { + for x = 0; x < cons.width; x++ { + expVal := ((y - lines) << 8) | x + if cons.fb[index] != expVal { + t.Errorf("[spec %d] expected value at (%d, %d) to be %d; got %d", specIndex, x, y, expVal, cons.fb[index]) + continue nextSpec + } + index++ + } + } + } +} + +func TestVgaWriteWithOffScreenCoords(t *testing.T) { + var cons = Vga{fb: make([]uint16, 80*25)} + cons.Init() + + specs := []struct { + x, y uint16 + }{ + {80, 25}, + {90, 24}, + {79, 30}, + {100, 100}, + } + +nextSpec: + for specIndex, spec := range specs { + for i := 0; i < len(cons.fb); i++ { + cons.fb[i] = 0 + } + + cons.Write('!', Red, spec.x, spec.y) + + for i := 0; i < len(cons.fb); i++ { + if got := cons.fb[i]; got != 0 { + t.Errorf("[spec %d] expected Write() with off-screen coords to be a no-op", specIndex) + continue nextSpec + } + } + } +} + +func TestVgaWrite(t *testing.T) { + var cons = Vga{fb: make([]uint16, 80*25)} + cons.Init() + + attr := (Black << 4) | Red + cons.Write('!', attr, 0, 0) + + expVal := uint16(attr<<8) | uint16('!') + if got := cons.fb[0]; got != expVal { + t.Errorf("expected call to Write() to set fb[0] to %d; got %d", expVal, got) + } +}