1
0
mirror of https://github.com/taigrr/gopher-os synced 2025-01-18 04:43:13 -08:00

Implement vesa console driver for 8bpp framebuffers

This commit is contained in:
Achilleas Anagnostopoulos 2017-07-09 16:39:56 +01:00
parent cbf0c82702
commit 13ba4bbbed
2 changed files with 1143 additions and 0 deletions

View File

@ -0,0 +1,328 @@
package console
import (
"gopheros/device"
"gopheros/device/video/console/font"
"gopheros/kernel"
"gopheros/kernel/hal/multiboot"
"gopheros/kernel/kfmt"
"gopheros/kernel/mem"
"gopheros/kernel/mem/pmm"
"gopheros/kernel/mem/vmm"
"image/color"
"io"
"reflect"
"unsafe"
)
type VesaFbConsole struct {
bpp uint32
fbPhysAddr uintptr
fb []uint8
// Console dimensions in pixels
width uint32
height uint32
// offsetY specifies a the pixel offset for the beginning for text.
// The rows of the framebuffer between 0 and offsetY are reserved and
// cannot be used for displaying text.
offsetY uint32
// Size of a row in bytes
pitch uint32
// Console dimensions in characters
font *font.Font
widthInChars uint32
heightInChars uint32
palette color.Palette
defaultFg uint8
defaultBg uint8
clearChar uint16
}
func NewVesaFbConsole(width, height uint32, bpp uint8, pitch uint32, fbPhysAddr uintptr) *VesaFbConsole {
return &VesaFbConsole{
bpp: uint32(bpp),
fbPhysAddr: fbPhysAddr,
width: width,
height: height,
pitch: pitch,
// light gray text on black background
defaultFg: 7,
defaultBg: 0,
clearChar: uint16(' '),
}
}
// SetFont selects a bitmap font to be used by the console.
func (cons *VesaFbConsole) SetFont(f *font.Font) {
if f == nil {
return
}
cons.font = f
cons.widthInChars = cons.width / uint32(f.GlyphWidth)
cons.heightInChars = (cons.height - cons.offsetY) / uint32(f.GlyphHeight)
}
// Dimensions returns the console width and height in the specified dimension.
func (cons *VesaFbConsole) Dimensions(dim Dimension) (uint32, uint32) {
switch dim {
case Characters:
return cons.widthInChars, cons.heightInChars
default:
return cons.width, cons.height
}
}
// DefaultColors returns the default foreground and background colors
// used by this console.
func (cons *VesaFbConsole) DefaultColors() (fg uint8, bg uint8) {
return cons.defaultFg, cons.defaultBg
}
// Fill sets the contents of the specified rectangular region to the requested
// color. Both x and y coordinates are 1-based.
func (cons *VesaFbConsole) Fill(x, y, width, height uint32, _, bg uint8) {
if cons.font == nil {
return
}
// clip rectangle
if x == 0 {
x = 1
} else if x >= cons.widthInChars {
x = cons.widthInChars
}
if y == 0 {
y = 1
} else if y >= cons.heightInChars {
y = cons.heightInChars
}
if x+width-1 > cons.widthInChars {
width = cons.widthInChars - x + 1
}
if y+height-1 > cons.heightInChars {
height = cons.heightInChars - y + 1
}
pX := (x - 1) * cons.font.GlyphWidth
pY := (y - 1) * cons.font.GlyphHeight
pW := width * cons.font.GlyphWidth
pH := height * cons.font.GlyphHeight
switch cons.bpp {
case 8:
cons.fill8(pX, pY, pW, pH, bg)
}
}
// fill8 implements a fill operation using an 8bpp framebuffer.
func (cons *VesaFbConsole) fill8(pX, pY, pW, pH uint32, bg uint8) {
fbRowOffset := cons.fbOffset(pX, pY)
for ; pH > 0; pH, fbRowOffset = pH-1, fbRowOffset+cons.pitch {
for fbOffset := fbRowOffset; fbOffset < fbRowOffset+pW; fbOffset++ {
cons.fb[fbOffset] = bg
}
}
}
// Scroll the console contents to the specified direction. The caller
// is responsible for updating (e.g. clear or replace) the contents of
// the region that was scrolled.
func (cons *VesaFbConsole) Scroll(dir ScrollDir, lines uint32) {
if cons.font == nil || lines == 0 || lines > cons.heightInChars {
return
}
offset := cons.fbOffset(0, lines*cons.font.GlyphHeight-cons.offsetY)
switch dir {
case ScrollDirUp:
startOffset := cons.fbOffset(0, 0)
endOffset := cons.fbOffset(0, cons.height-lines*cons.font.GlyphHeight-cons.offsetY)
for i := startOffset; i < endOffset; i++ {
cons.fb[i] = cons.fb[i+offset]
}
case ScrollDirDown:
startOffset := cons.fbOffset(0, lines*cons.font.GlyphHeight)
for i := uint32(len(cons.fb) - 1); i >= startOffset; i-- {
cons.fb[i] = cons.fb[i-offset]
}
}
}
// Write a char to the specified location. If fg or bg exceed the supported
// colors for this console, they will be set to their default value. Both x and
// y coordinates are 1-based
func (cons *VesaFbConsole) Write(ch byte, fg, bg uint8, x, y uint32) {
if x < 1 || x > cons.widthInChars || y < 1 || y > cons.heightInChars || cons.font == nil {
return
}
pX := (x - 1) * cons.font.GlyphWidth
pY := (y - 1) * cons.font.GlyphHeight
switch cons.bpp {
case 8:
cons.write8(ch, fg, bg, pX, pY)
}
}
// write8 writes a charactero using an 8bpp framebuffer.
func (cons *VesaFbConsole) write8(glyphIndex, fg, bg uint8, pX, pY uint32) {
var (
fontOffset = uint32(glyphIndex) * cons.font.BytesPerRow * cons.font.GlyphHeight
fbRowOffset = cons.fbOffset(pX, pY)
fbOffset uint32
x, y uint32
mask uint8
)
for y = 0; y < cons.font.GlyphHeight; y, fbRowOffset, fontOffset = y+1, fbRowOffset+cons.pitch, fontOffset+1 {
fbOffset = fbRowOffset
fontRowData := cons.font.Data[fontOffset]
mask = 1 << 7
for x = 0; x < cons.font.GlyphWidth; x, fbOffset, mask = x+1, fbOffset+1, mask>>1 {
// If mask becomes zero while we are still in this loop
// then the font uses > 1 byte per row. We need to
// fetch the next byte and reset the mask.
if mask == 0 {
fontOffset++
fontRowData = cons.font.Data[fontOffset]
mask = 1 << 7
}
if (fontRowData & mask) != 0 {
cons.fb[fbOffset] = fg
} else {
cons.fb[fbOffset] = bg
}
}
}
}
// fbOffset returns the linear offset into the framebuffer that corresponds to
// the pixel at (x,y).
func (cons *VesaFbConsole) fbOffset(x, y uint32) uint32 {
return ((y + cons.offsetY) * cons.pitch) + (x * cons.bpp >> 3)
}
// Palette returns the active color palette for this console.
func (cons *VesaFbConsole) Palette() color.Palette {
return cons.palette
}
// SetPaletteColor updates the color definition for the specified
// palette index. Passing a color index greated than the number of
// supported colors should be a no-op.
func (cons *VesaFbConsole) SetPaletteColor(index uint8, rgba color.RGBA) {
cons.palette[index] = rgba
// Only program the DAC when we are in indexed (8bpp) mode
if cons.bpp > 8 {
return
}
// Load palette entry to the DAC. Each DAC entry is a 6-bit value so
// we need to scale the RGB values in the [0-63] range.
portWriteByteFn(0x3c8, index)
portWriteByteFn(0x3c9, rgba.R>>2)
portWriteByteFn(0x3c9, rgba.G>>2)
portWriteByteFn(0x3c9, rgba.B>>2)
}
// loadDefaultPalette is called during driver initialization to setup the
// console palette. Regardless of the framebuffer depth, the console always
// uses a 256-color palette.
func (cons *VesaFbConsole) loadDefaultPalette() {
cons.palette = make(color.Palette, 256)
egaPalette := []color.RGBA{
color.RGBA{R: 0, G: 0, B: 0}, /* black */
color.RGBA{R: 0, G: 0, B: 128}, /* blue */
color.RGBA{R: 0, G: 128, B: 1}, /* green */
color.RGBA{R: 0, G: 128, B: 128}, /* cyan */
color.RGBA{R: 128, G: 0, B: 1}, /* red */
color.RGBA{R: 128, G: 0, B: 128}, /* magenta */
color.RGBA{R: 64, G: 64, B: 1}, /* brown */
color.RGBA{R: 128, G: 128, B: 128}, /* light gray */
color.RGBA{R: 64, G: 64, B: 64}, /* dark gray */
color.RGBA{R: 0, G: 0, B: 255}, /* light blue */
color.RGBA{R: 0, G: 255, B: 1}, /* light green */
color.RGBA{R: 0, G: 255, B: 255}, /* light cyan */
color.RGBA{R: 255, G: 0, B: 1}, /* light red */
color.RGBA{R: 255, G: 0, B: 255}, /* light magenta */
color.RGBA{R: 255, G: 255, B: 1}, /* yellow */
color.RGBA{R: 255, G: 255, B: 255}, /* white */
}
// Load default EFA palette for colors 0-16
var index int
for ; index < len(egaPalette); index++ {
cons.SetPaletteColor(uint8(index), egaPalette[index])
}
// Set all other colors to black
for ; index < len(cons.palette); index++ {
cons.SetPaletteColor(uint8(index), egaPalette[0])
}
}
// DriverName returns the name of this driver.
func (cons *VesaFbConsole) DriverName() string {
return "vesa_fb_console"
}
// DriverVersion returns the version of this driver.
func (cons *VesaFbConsole) DriverVersion() (uint16, uint16, uint16) {
return 0, 0, 1
}
// DriverInit initializes this driver.
func (cons *VesaFbConsole) DriverInit(w io.Writer) *kernel.Error {
// Map the framebuffer so we can write to it
fbSize := mem.Size(cons.height * cons.pitch)
fbPage, err := mapRegionFn(
pmm.Frame(cons.fbPhysAddr>>mem.PageShift),
fbSize,
vmm.FlagPresent|vmm.FlagRW,
)
if err != nil {
return err
}
cons.fb = *(*[]uint8)(unsafe.Pointer(&reflect.SliceHeader{
Len: int(fbSize),
Cap: int(fbSize),
Data: fbPage.Address(),
}))
kfmt.Fprintf(w, "mapped framebuffer to 0x%x\n", fbPage.Address())
cons.loadDefaultPalette()
return nil
}
// probeForVesaFbConsole checks for the presence of a vga text console.
func probeForVesaFbConsole() device.Driver {
var drv device.Driver
fbInfo := getFramebufferInfoFn()
if fbInfo.Type == multiboot.FramebufferTypeIndexed {
drv = NewVesaFbConsole(fbInfo.Width, fbInfo.Height, fbInfo.Bpp, fbInfo.Pitch, uintptr(fbInfo.PhysAddr))
}
return drv
}
func init() {
ProbeFuncs = append(ProbeFuncs, probeForVesaFbConsole)
}

View File

@ -0,0 +1,815 @@
package console
import (
"bytes"
"fmt"
"gopheros/device"
"gopheros/device/video/console/font"
"gopheros/kernel"
"gopheros/kernel/cpu"
"gopheros/kernel/hal/multiboot"
"gopheros/kernel/mem"
"gopheros/kernel/mem/pmm"
"gopheros/kernel/mem/vmm"
"image/color"
"reflect"
"strings"
"testing"
)
func TestVesaFbTextDimensions(t *testing.T) {
var cons Device = NewVesaFbConsole(16, 32, 8, 16, 0)
if w, h := cons.Dimensions(Characters); w != 0 || h != 0 {
t.Fatalf("expected console dimensions to be 0x0 before setting a font; got %dx%d", w, h)
}
specs := []struct {
offsetY uint32
font *font.Font
expW, expH uint32
}{
{0, mockFont8x10, 2, 3},
{6, mockFont8x10, 2, 2},
}
// Setting a nil font should be a no-op
cons.(FontSetter).SetFont(nil)
if w, h := cons.Dimensions(Characters); w != 0 || h != 0 {
t.Fatalf("expected console character dimensions to be 0x0; got %dx%d", w, h)
}
for specIndex, spec := range specs {
cons.(*VesaFbConsole).offsetY = spec.offsetY
cons.(FontSetter).SetFont(spec.font)
if w, h := cons.Dimensions(Characters); w != spec.expW || h != spec.expH {
t.Fatalf("[spec %d] expected console character dimensions to be %dx%d; got %dx%d", specIndex, spec.expW, spec.expH, w, h)
}
if w, h := cons.Dimensions(Pixels); w != 16 || h != 32 {
t.Fatalf("[spec %d] expected console pixel dimensions to be 16x32; got %dx%d", specIndex, w, h)
}
}
}
func TestVesaFbDefaultColors(t *testing.T) {
var cons Device = NewVesaFbConsole(16, 32, 8, 16, 0)
if fg, bg := cons.DefaultColors(); fg != 7 || bg != 0 {
t.Fatalf("expected console default colors to be fg:7, bg:0; got fg:%d, bg: %d", fg, bg)
}
}
func TestVesaFbWrite8bpp(t *testing.T) {
specs := []struct {
consW, consH, offsetY uint32
font *font.Font
expFb []byte
}{
{
16, 16, 6,
mockFont8x10,
[]byte("" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000010000" +
"0000000000111000" +
"0000000001101100" +
"0000000011000110" +
"0000000011000110" +
"0000000011111110" +
"0000000011000110" +
"0000000011000110" +
"0000000011000110" +
"0000000011000110",
),
},
{
20, 20, 3,
mockFont10x14,
[]byte("" +
"00000000000000000000" +
"00000000000000000000" +
"00000000000000000000" +
"00000000000000010000" +
"00000000000000010000" +
"00000000000000111000" +
"00000000000000111000" +
"00000000000001101100" +
"00000000000001101100" +
"00000000000001100110" +
"00000000000011000110" +
"00000000000011111110" +
"00000000000011000110" +
"00000000000110000110" +
"00000000000110000011" +
"00000000000110000011" +
"00000000001111000111" +
"00000000000000000000" +
"00000000000000000000" +
"00000000000000000000",
),
},
}
var (
fg = uint8(1)
bg = uint8(0)
)
for specIndex, spec := range specs {
fb := make([]uint8, spec.consW*spec.consH)
cons := NewVesaFbConsole(spec.consW, spec.consH, 8, spec.consW, 0)
cons.fb = fb
cons.offsetY = spec.offsetY
cons.SetFont(spec.font)
// ASCII 0 maps to the a blank character in the mock font
// ASCII 1 maps to the letter 'A' in the mock font
cons.Write(0, fg, bg, 0, 0)
cons.Write(1, fg, bg, 2, 1)
// Convert expected contents from ASCII to byte
for i := 0; i < len(spec.expFb); i++ {
spec.expFb[i] -= '0'
}
if !reflect.DeepEqual(spec.expFb, fb) {
t.Errorf("[spec %d] unexpected frame buffer contents:\n%s",
specIndex,
diffFrameBuffer(spec.consW, spec.consH, spec.consW, spec.expFb, fb),
)
}
}
}
func TestVesaFbScroll8bpp(t *testing.T) {
var (
consW, consH uint32 = 16, 16
offsetY uint32 = 3
origFb = []byte("" +
"6666666666666666" + // }
"7777777777777777" + // }- reserved rows
"8888888888888888" + // }
"0000000000001000" +
"0000000000010000" +
"0000000000100000" +
"0000000001000000" +
"0000000010000000" +
"0000000100000000" +
"0000001000000000" +
"0000010000000000" +
"0000100000000000" +
"0001000000000000" +
"0010000000000000" +
"0100000000000000" +
"1000000000000000",
)
)
specs := []struct {
dir ScrollDir
lines uint32
expFb []byte
}{
{
ScrollDirUp,
0,
[]byte("" +
"6666666666666666" + // }
"7777777777777777" + // }- reserved rows
"8888888888888888" + // }
"0000000000001000" +
"0000000000010000" +
"0000000000100000" +
"0000000001000000" +
"0000000010000000" +
"0000000100000000" +
"0000001000000000" +
"0000010000000000" +
"0000100000000000" +
"0001000000000000" +
"0010000000000000" +
"0100000000000000" +
"1000000000000000",
),
},
{
ScrollDirUp,
10000,
[]byte("" +
"6666666666666666" + // }
"7777777777777777" + // }- reserved rows
"8888888888888888" + // }
"0000000000001000" +
"0000000000010000" +
"0000000000100000" +
"0000000001000000" +
"0000000010000000" +
"0000000100000000" +
"0000001000000000" +
"0000010000000000" +
"0000100000000000" +
"0001000000000000" +
"0010000000000000" +
"0100000000000000" +
"1000000000000000",
),
},
{
ScrollDirUp,
1,
[]byte("" +
"6666666666666666" + // }
"7777777777777777" + // }- reserved rows
"8888888888888888" + // }
"0000000000010000" +
"0000000000100000" +
"0000000001000000" +
"0000000010000000" +
"0000000100000000" +
"0000001000000000" +
"0000010000000000" +
"0000100000000000" +
"0001000000000000" +
"0010000000000000" +
"0100000000000000" +
"1000000000000000" +
"1000000000000000",
),
},
{
ScrollDirUp,
2,
[]byte("" +
"6666666666666666" + // }
"7777777777777777" + // }- reserved rows
"8888888888888888" + // }
"0000000000100000" +
"0000000001000000" +
"0000000010000000" +
"0000000100000000" +
"0000001000000000" +
"0000010000000000" +
"0000100000000000" +
"0001000000000000" +
"0010000000000000" +
"0100000000000000" +
"1000000000000000" +
"0100000000000000" +
"1000000000000000",
),
},
{
ScrollDirDown,
1,
[]byte("" +
"6666666666666666" + // }
"7777777777777777" + // }- reserved rows
"8888888888888888" + // }
"0000000000001000" +
"0000000000001000" +
"0000000000010000" +
"0000000000100000" +
"0000000001000000" +
"0000000010000000" +
"0000000100000000" +
"0000001000000000" +
"0000010000000000" +
"0000100000000000" +
"0001000000000000" +
"0010000000000000" +
"0100000000000000",
),
},
{
ScrollDirDown,
2,
[]byte("" +
"6666666666666666" + // }
"7777777777777777" + // }- reserved rows
"8888888888888888" + // }
"0000000000001000" +
"0000000000010000" +
"0000000000001000" +
"0000000000010000" +
"0000000000100000" +
"0000000001000000" +
"0000000010000000" +
"0000000100000000" +
"0000001000000000" +
"0000010000000000" +
"0000100000000000" +
"0001000000000000" +
"0010000000000000",
),
},
}
// Convert original fb contents from ASCII to byte
for i := 0; i < len(origFb); i++ {
origFb[i] -= '0'
}
for specIndex, spec := range specs {
// Convert expected contents from ASCII to byte
for i := 0; i < len(spec.expFb); i++ {
spec.expFb[i] -= '0'
}
fb := make([]uint8, consW*consH)
copy(fb, origFb)
cons := NewVesaFbConsole(consW, consH, 8, consW, 0)
cons.fb = fb
cons.offsetY = offsetY
// calling scroll before setting the font should be a no-op
cons.Scroll(spec.dir, spec.lines)
if !reflect.DeepEqual(origFb, fb) {
t.Errorf("[spec %d] unexpected frame buffer contents:\n%s",
specIndex,
diffFrameBuffer(consW, consH, consW, origFb, fb),
)
}
cons.SetFont(&font.Font{
GlyphWidth: 8,
GlyphHeight: 1,
BytesPerRow: 1,
})
cons.Scroll(spec.dir, spec.lines)
if !reflect.DeepEqual(spec.expFb, fb) {
t.Errorf("[spec %d] unexpected frame buffer contents:\n%s",
specIndex,
diffFrameBuffer(consW, consH, consW, spec.expFb, fb),
)
}
}
}
func TestVesFbFill8(t *testing.T) {
var (
consW, consH uint32 = 16, 26
bg uint8 = 1
origFb = []byte("" +
"6666666666666666" + // }
"7777777777777777" + // }- reserved rows
"8888888888888888" + // }
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000",
)
)
specs := []struct {
// Input rect in characters
x, y, w, h uint32
offsetY uint32
expFb []byte
}{
{
0, 0, 1, 1,
0,
[]byte("" +
"1111111166666666" + // }
"1111111177777777" + // }- reserved rows
"1111111188888888" + // }
"1111111100000000" +
"1111111100000000" +
"1111111100000000" +
"1111111100000000" +
"1111111100000000" +
"1111111100000000" +
"1111111100000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000",
),
},
{
2, 0, 10, 1,
3,
[]byte("" +
"6666666666666666" + // }
"7777777777777777" + // }- reserved rows
"8888888888888888" + // }
"0000000011111111" +
"0000000011111111" +
"0000000011111111" +
"0000000011111111" +
"0000000011111111" +
"0000000011111111" +
"0000000011111111" +
"0000000011111111" +
"0000000011111111" +
"0000000011111111" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000",
),
},
{
0, 0, 100, 100,
3,
[]byte("" +
"6666666666666666" + // }
"7777777777777777" + // }- reserved rows
"8888888888888888" + // }
"1111111111111111" +
"1111111111111111" +
"1111111111111111" +
"1111111111111111" +
"1111111111111111" +
"1111111111111111" +
"1111111111111111" +
"1111111111111111" +
"1111111111111111" +
"1111111111111111" +
"1111111111111111" +
"1111111111111111" +
"1111111111111111" +
"1111111111111111" +
"1111111111111111" +
"1111111111111111" +
"1111111111111111" +
"1111111111111111" +
"1111111111111111" +
"1111111111111111" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000",
),
},
{
100, 100, 1, 1,
6,
[]byte("" +
"6666666666666666" + // }
"7777777777777777" + // }- reserved rows
"8888888888888888" + // }
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000000000000" +
"0000000011111111" +
"0000000011111111" +
"0000000011111111" +
"0000000011111111" +
"0000000011111111" +
"0000000011111111" +
"0000000011111111" +
"0000000011111111" +
"0000000011111111" +
"0000000011111111",
),
},
}
// Convert original fb contents from ASCII to byte
for i := 0; i < len(origFb); i++ {
origFb[i] -= '0'
}
for specIndex, spec := range specs {
// Convert expected contents from ASCII to byte
for i := 0; i < len(spec.expFb); i++ {
spec.expFb[i] -= '0'
}
fb := make([]uint8, consW*consH)
copy(fb, origFb)
cons := NewVesaFbConsole(consW, consH, 8, consW, 0)
cons.fb = fb
cons.offsetY = spec.offsetY
// Calling fill before selecting a font should be a no-op
cons.Fill(spec.x, spec.y, spec.w, spec.h, 0, bg)
if !reflect.DeepEqual(origFb, fb) {
t.Errorf("[spec %d] unexpected frame buffer contents:\n%s",
specIndex,
diffFrameBuffer(consW, consH, consW, origFb, fb),
)
}
cons.SetFont(mockFont8x10)
cons.Fill(spec.x, spec.y, spec.w, spec.h, 0, bg)
if !reflect.DeepEqual(spec.expFb, fb) {
t.Errorf("[spec %d] unexpected frame buffer contents:\n%s",
specIndex,
diffFrameBuffer(consW, consH, consW, spec.expFb, fb),
)
}
}
}
func TestVesaFbPalette(t *testing.T) {
defer func() {
portWriteByteFn = cpu.PortWriteByte
}()
expPal := make(color.Palette, 0)
expPal = append(expPal,
color.RGBA{R: 0, G: 0, B: 0}, /* black */
color.RGBA{R: 0, G: 0, B: 128}, /* blue */
color.RGBA{R: 0, G: 128, B: 1}, /* green */
color.RGBA{R: 0, G: 128, B: 128}, /* cyan */
color.RGBA{R: 128, G: 0, B: 1}, /* red */
color.RGBA{R: 128, G: 0, B: 128}, /* magenta */
color.RGBA{R: 64, G: 64, B: 1}, /* brown */
color.RGBA{R: 128, G: 128, B: 128}, /* light gray */
color.RGBA{R: 64, G: 64, B: 64}, /* dark gray */
color.RGBA{R: 0, G: 0, B: 255}, /* light blue */
color.RGBA{R: 0, G: 255, B: 1}, /* light green */
color.RGBA{R: 0, G: 255, B: 255}, /* light cyan */
color.RGBA{R: 255, G: 0, B: 1}, /* light red */
color.RGBA{R: 255, G: 0, B: 255}, /* light magenta */
color.RGBA{R: 255, G: 255, B: 1}, /* yellow */
color.RGBA{R: 255, G: 255, B: 255}, /* white */
)
for i := len(expPal); i < 256; i++ {
expPal = append(expPal, expPal[0])
}
var (
dacIndex uint8
compIndex uint8
)
portWriteByteFn = func(port uint16, val uint8) {
switch port {
case 0x3c8:
dacIndex = val
compIndex = 0
case 0x3c9:
r, g, b, _ := expPal[dacIndex].RGBA()
var expVal uint8
switch compIndex {
case 0:
expVal = uint8(r) >> 2
case 1:
expVal = uint8(g) >> 2
case 2:
expVal = uint8(b) >> 2
}
if val != expVal {
t.Errorf("expected component %d for DAC entry %d to be %d; got %d", compIndex, dacIndex, expVal, val)
}
compIndex++
}
}
cons := NewVesaFbConsole(0, 0, 8, 0, 0)
cons.loadDefaultPalette()
customColor := color.RGBA{R: 251, G: 252, B: 253}
expPal[255] = customColor
cons.SetPaletteColor(255, customColor)
got := cons.Palette()
for index, exp := range expPal {
if got[index] != exp {
t.Errorf("palette entry %d: want %v; got %v", index, exp, got[index])
}
}
}
func TestVesaFbDriverInterface(t *testing.T) {
defer func() {
mapRegionFn = vmm.MapRegion
portWriteByteFn = cpu.PortWriteByte
}()
var dev device.Driver = NewVesaFbConsole(320, 200, 8, 320, uintptr(0xa0000))
if dev.DriverName() == "" {
t.Fatal("DriverName() returned an empty string")
}
if major, minor, patch := dev.DriverVersion(); major+minor+patch == 0 {
t.Fatal("DriverVersion() returned an invalid version number")
}
t.Run("init success", func(t *testing.T) {
mapRegionFn = func(_ pmm.Frame, _ mem.Size, _ vmm.PageTableEntryFlag) (vmm.Page, *kernel.Error) {
return 0xa0000, nil
}
portWriteByteFn = func(_ uint16, _ uint8) {}
if err := dev.DriverInit(nil); err != nil {
t.Fatal(err)
}
})
t.Run("init fail", func(t *testing.T) {
expErr := &kernel.Error{Module: "test", Message: "something went wrong"}
mapRegionFn = func(_ pmm.Frame, _ mem.Size, _ vmm.PageTableEntryFlag) (vmm.Page, *kernel.Error) {
return 0, expErr
}
if err := dev.DriverInit(nil); err != expErr {
t.Fatalf("expected error: %v; got %v", expErr, err)
}
})
}
func TestVesaFbProbe(t *testing.T) {
defer func() {
getFramebufferInfoFn = multiboot.GetFramebufferInfo
}()
getFramebufferInfoFn = func() *multiboot.FramebufferInfo {
return &multiboot.FramebufferInfo{
Width: 320,
Height: 20,
Pitch: 320,
Bpp: 8,
PhysAddr: 0xa0000,
Type: multiboot.FramebufferTypeIndexed,
}
}
if drv := probeForVesaFbConsole(); drv == nil {
t.Fatal("expected probeForVesaFbConsole to return a driver")
}
}
func dumpFramebuffer(consW, consH, consPitch uint32, fb []byte) string {
var buf bytes.Buffer
for y := uint32(0); y < consH; y++ {
fmt.Fprintf(&buf, "%04d |", y)
index := (y * consPitch)
for x := uint32(0); x < consPitch; x++ {
fmt.Fprintf(&buf, "%d", fb[index+x])
}
fmt.Fprintln(&buf, "|")
}
return strings.TrimSpace(buf.String())
}
func diffFrameBuffer(consW, consH, consPitch uint32, exp, got []byte) string {
expDump := strings.Split(dumpFramebuffer(consW, consH, consPitch, exp), "\n")
gotDump := strings.Split(dumpFramebuffer(consW, consH, consPitch, got), "\n")
maxLines := len(expDump)
if l := len(gotDump); l > maxLines {
maxLines = l
}
var buf bytes.Buffer
var left, right string
buf.WriteString("exp:")
buf.WriteString(strings.Repeat(" ", len(expDump[0])-4))
buf.WriteString(" | got:\n")
for line := 0; line < maxLines; line++ {
if line < len(expDump) {
left = expDump[line]
} else {
left = ""
}
if line < len(gotDump) {
right = gotDump[line]
} else {
right = ""
}
fmt.Fprintf(&buf, "%s | %s\n", left, right)
}
return buf.String()
}
var mockFont8x10 = &font.Font{
GlyphWidth: 8,
GlyphHeight: 10,
BytesPerRow: 1,
Data: []byte{
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
// glyph 1
0x10, /* 00010000 */
0x38, /* 00111000 */
0x6c, /* 01101100 */
0xc6, /* 11000110 */
0xc6, /* 11000110 */
0xfe, /* 11111110 */
0xc6, /* 11000110 */
0xc6, /* 11000110 */
0xc6, /* 11000110 */
0xc6, /* 11000110 */
},
}
var mockFont10x14 = &font.Font{
GlyphWidth: 10,
GlyphHeight: 14,
BytesPerRow: 2,
Data: []byte{
0x00, 0x00, /* 0000000000 */
0x00, 0x00, /* 0000000000 */
0x00, 0x00, /* 0000000000 */
0x00, 0x00, /* 0000000000 */
0x00, 0x00, /* 0000000000 */
0x00, 0x00, /* 0000000000 */
0x00, 0x00, /* 0000000000 */
0x00, 0x00, /* 0000000000 */
0x00, 0x00, /* 0000000000 */
0x00, 0x00, /* 0000000000 */
0x00, 0x00, /* 0000000000 */
0x00, 0x00, /* 0000000000 */
0x00, 0x00, /* 0000000000 */
0x00, 0x00, /* 0000000000 */
// glyph 1
0x04, 0x00, /* 0000010000 */
0x04, 0x00, /* 0000010000 */
0x0e, 0x00, /* 0000111000 */
0x0e, 0x00, /* 0000111000 */
0x1b, 0x00, /* 0001101100 */
0x1b, 0x00, /* 0001101100 */
0x19, 0x80, /* 0001100110 */
0x31, 0x80, /* 0011000110 */
0x3f, 0x80, /* 0011111110 */
0x31, 0x80, /* 0011000110 */
0x61, 0x80, /* 0110000110 */
0x60, 0xc0, /* 0110000011 */
0x60, 0xc0, /* 0110000011 */
0xf1, 0xc0, /* 1111000111 */
},
}