mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
Merge pull request #1 from achilleasa/implement-text-console
Implement VGA text console driver
This commit is contained in:
commit
8c41bf6404
52
kernel/driver/video/console/console.go
Normal file
52
kernel/driver/video/console/console.go
Normal file
@ -0,0 +1,52 @@
|
||||
package console
|
||||
|
||||
import "sync"
|
||||
|
||||
// Attr defines a color attribute.
|
||||
type Attr uint16
|
||||
|
||||
// The set of attributes that can be passed to Write().
|
||||
const (
|
||||
Black Attr = iota
|
||||
Blue
|
||||
Green
|
||||
Cyan
|
||||
Red
|
||||
Magenta
|
||||
Brown
|
||||
LightGrey
|
||||
Grey
|
||||
LightBlue
|
||||
LightGreen
|
||||
LightCyan
|
||||
LightRed
|
||||
LightMagenta
|
||||
LightBrown
|
||||
White
|
||||
)
|
||||
|
||||
// ScrollDir defines a scroll direction.
|
||||
type ScrollDir uint8
|
||||
|
||||
// The supported list of scroll directions for the console Scroll() calls.
|
||||
const (
|
||||
Up ScrollDir = iota
|
||||
Down
|
||||
)
|
||||
|
||||
// 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.
|
||||
Dimensions() (uint16, uint16)
|
||||
|
||||
// Clear clears the specified rectangular region
|
||||
Clear(x, y, width, height uint16)
|
||||
|
||||
// Scroll a particular number of lines to the specified direction.
|
||||
Scroll(dir ScrollDir, lines uint16)
|
||||
|
||||
// Write a char to the specified location.
|
||||
Write(ch byte, attr Attr, x, y uint16)
|
||||
}
|
106
kernel/driver/video/console/vga.go
Normal file
106
kernel/driver/video/console/vga.go
Normal file
@ -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)
|
||||
}
|
204
kernel/driver/video/console/vga_test.go
Normal file
204
kernel/driver/video/console/vga_test.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user