mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
Merge pull request #38 from achilleasa/refactor-console-and-tty-drivers
Refactor console and tty drivers
This commit is contained in:
commit
d804b17ed8
1
Makefile
1
Makefile
@ -170,6 +170,7 @@ lint: lint-check-deps
|
|||||||
--deadline 300s \
|
--deadline 300s \
|
||||||
--exclude 'return value not checked' \
|
--exclude 'return value not checked' \
|
||||||
--exclude 'possible misuse of unsafe.Pointer' \
|
--exclude 'possible misuse of unsafe.Pointer' \
|
||||||
|
--exclude 'x \^ 0 always equals x' \
|
||||||
./...
|
./...
|
||||||
|
|
||||||
lint-check-deps:
|
lint-check-deps:
|
||||||
|
19
src/gopheros/device/driver.go
Normal file
19
src/gopheros/device/driver.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package device
|
||||||
|
|
||||||
|
import "gopheros/kernel"
|
||||||
|
|
||||||
|
// Driver is an interface implemented by all drivers.
|
||||||
|
type Driver interface {
|
||||||
|
// DriverName returns the name of the driver.
|
||||||
|
DriverName() string
|
||||||
|
|
||||||
|
// DriverVersion returns the driver version.
|
||||||
|
DriverVersion() (major uint16, minor uint16, patch uint16)
|
||||||
|
|
||||||
|
// DriverInit initializes the device driver.
|
||||||
|
DriverInit() *kernel.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProbeFn is a function that scans for the presence of a particular
|
||||||
|
// piece of hardware and returns a driver for it.
|
||||||
|
type ProbeFn func() Driver
|
52
src/gopheros/device/tty/device.go
Normal file
52
src/gopheros/device/tty/device.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package tty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gopheros/device/video/console"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultScrollback defines the terminal scrollback in lines.
|
||||||
|
DefaultScrollback = 80
|
||||||
|
|
||||||
|
// DefaultTabWidth defines the number of spaces that tabs expand to.
|
||||||
|
DefaultTabWidth = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// State defines the supported terminal state values.
|
||||||
|
type State uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// StateInactive marks the terminal as inactive. Any writes will be
|
||||||
|
// buffered and not synced to the attached console.
|
||||||
|
StateInactive State = iota
|
||||||
|
|
||||||
|
// StateActive marks the terminal as active. Any writes will be
|
||||||
|
// buffered and also synced to the attached console.
|
||||||
|
StateActive
|
||||||
|
)
|
||||||
|
|
||||||
|
// Device is implemented by objects that can be used as a terminal device.
|
||||||
|
type Device interface {
|
||||||
|
io.Writer
|
||||||
|
io.ByteWriter
|
||||||
|
|
||||||
|
// AttachTo connects a TTY to a console instance.
|
||||||
|
AttachTo(console.Device)
|
||||||
|
|
||||||
|
// State returns the TTY's state.
|
||||||
|
State() State
|
||||||
|
|
||||||
|
// SetState updates the TTY's state.
|
||||||
|
SetState(State)
|
||||||
|
|
||||||
|
// CursorPosition returns the current cursor x,y coordinates. Both
|
||||||
|
// coordinates are 1-based (top-left corner has coordinates 1,1).
|
||||||
|
CursorPosition() (uint16, uint16)
|
||||||
|
|
||||||
|
// SetCursorPosition sets the current cursor position to (x,y). Both
|
||||||
|
// coordinates are 1-based (top-left corner has coordinates 1,1).
|
||||||
|
// Implementations are expected to clip the cursor position to their
|
||||||
|
// viewport.
|
||||||
|
SetCursorPosition(x, y uint16)
|
||||||
|
}
|
11
src/gopheros/device/tty/probe.go
Normal file
11
src/gopheros/device/tty/probe.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package tty
|
||||||
|
|
||||||
|
import "gopheros/device"
|
||||||
|
|
||||||
|
// HWProbes returns a slice of device.ProbeFn that can be used by the hal
|
||||||
|
// package to probe for TTY device hardware.
|
||||||
|
func HWProbes() []device.ProbeFn {
|
||||||
|
return []device.ProbeFn{
|
||||||
|
probeForVT,
|
||||||
|
}
|
||||||
|
}
|
265
src/gopheros/device/tty/vt.go
Normal file
265
src/gopheros/device/tty/vt.go
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
package tty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gopheros/device"
|
||||||
|
"gopheros/device/video/console"
|
||||||
|
"gopheros/kernel"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VT implements a terminal supporting scrollback. The terminal interprets the
|
||||||
|
// following special characters:
|
||||||
|
// - \r (carriage-return)
|
||||||
|
// - \n (line-feed)
|
||||||
|
// - \b (backspace)
|
||||||
|
// - \t (tab; expanded to tabWidth spaces)
|
||||||
|
type VT struct {
|
||||||
|
cons console.Device
|
||||||
|
|
||||||
|
// Terminal dimensions
|
||||||
|
termWidth uint16
|
||||||
|
termHeight uint16
|
||||||
|
viewportWidth uint16
|
||||||
|
viewportHeight uint16
|
||||||
|
|
||||||
|
// The number of additional lines of output that are buffered by the
|
||||||
|
// terminal to support scrolling up.
|
||||||
|
scrollback uint16
|
||||||
|
|
||||||
|
// The terminal contents. Each character occupies 3 bytes and uses the
|
||||||
|
// format: (ASCII char, fg, bg)
|
||||||
|
data []uint8
|
||||||
|
|
||||||
|
// Terminal state.
|
||||||
|
tabWidth uint8
|
||||||
|
defaultFg, curFg uint8
|
||||||
|
defaultBg, curBg uint8
|
||||||
|
cursorX uint16
|
||||||
|
cursorY uint16
|
||||||
|
viewportY uint16
|
||||||
|
dataOffset uint
|
||||||
|
state State
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVT creates a new virtual terminal device. The tabWidth parameter controls
|
||||||
|
// tab expansion whereas the scrollback parameter defines the line count that
|
||||||
|
// gets buffered by the terminal to provide scrolling beyond the console
|
||||||
|
// height.
|
||||||
|
func NewVT(tabWidth uint8, scrollback uint16) *VT {
|
||||||
|
return &VT{
|
||||||
|
tabWidth: tabWidth,
|
||||||
|
scrollback: scrollback,
|
||||||
|
cursorX: 1,
|
||||||
|
cursorY: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachTo connects a TTY to a console instance.
|
||||||
|
func (t *VT) AttachTo(cons console.Device) {
|
||||||
|
if cons == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.cons = cons
|
||||||
|
t.viewportWidth, t.viewportHeight = cons.Dimensions()
|
||||||
|
t.viewportY = 0
|
||||||
|
t.defaultFg, t.defaultBg = cons.DefaultColors()
|
||||||
|
t.curFg, t.curBg = t.defaultFg, t.defaultBg
|
||||||
|
t.termWidth, t.termHeight = t.viewportWidth, t.viewportHeight+t.scrollback
|
||||||
|
t.cursorX, t.cursorY = 1, 1
|
||||||
|
|
||||||
|
// Allocate space for the contents and fill it with empty characters
|
||||||
|
// using the default fg/bg colors for the attached console.
|
||||||
|
t.data = make([]uint8, t.termWidth*t.termHeight*3)
|
||||||
|
for i := 0; i < len(t.data); i += 3 {
|
||||||
|
t.data[i] = ' '
|
||||||
|
t.data[i+1] = t.defaultFg
|
||||||
|
t.data[i+2] = t.defaultBg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// State returns the TTY's state.
|
||||||
|
func (t *VT) State() State {
|
||||||
|
return t.state
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetState updates the TTY's state.
|
||||||
|
func (t *VT) SetState(newState State) {
|
||||||
|
if t.state == newState {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.state = newState
|
||||||
|
|
||||||
|
// If the terminal became active, update the console with its contents
|
||||||
|
if t.state == StateActive && t.cons != nil {
|
||||||
|
for y := uint16(1); y <= t.viewportHeight; y++ {
|
||||||
|
offset := (y - 1 + t.viewportY) * (t.viewportWidth * 3)
|
||||||
|
for x := uint16(1); x <= t.viewportWidth; x, offset = x+1, offset+3 {
|
||||||
|
t.cons.Write(t.data[offset], t.data[offset+1], t.data[offset+2], x, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CursorPosition returns the current cursor position.
|
||||||
|
func (t *VT) CursorPosition() (uint16, uint16) {
|
||||||
|
return t.cursorX, t.cursorY
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCursorPosition sets the current cursor position to (x,y).
|
||||||
|
func (t *VT) SetCursorPosition(x, y uint16) {
|
||||||
|
if t.cons == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if x < 1 {
|
||||||
|
x = 1
|
||||||
|
} else if x > t.viewportWidth {
|
||||||
|
x = t.viewportWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
if y < 1 {
|
||||||
|
y = 1
|
||||||
|
} else if y > t.viewportHeight {
|
||||||
|
y = t.viewportHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
t.cursorX, t.cursorY = x, y
|
||||||
|
t.updateDataOffset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements io.Writer.
|
||||||
|
func (t *VT) Write(data []byte) (int, error) {
|
||||||
|
for count, b := range data {
|
||||||
|
err := t.WriteByte(b)
|
||||||
|
if err != nil {
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteByte implements io.ByteWriter.
|
||||||
|
func (t *VT) WriteByte(b byte) error {
|
||||||
|
if t.cons == nil {
|
||||||
|
return io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
|
||||||
|
switch b {
|
||||||
|
case '\r':
|
||||||
|
t.cr()
|
||||||
|
case '\n':
|
||||||
|
t.lf(true)
|
||||||
|
case '\b':
|
||||||
|
if t.cursorX > 1 {
|
||||||
|
t.SetCursorPosition(t.cursorX-1, t.cursorY)
|
||||||
|
t.doWrite(' ', false)
|
||||||
|
}
|
||||||
|
case '\t':
|
||||||
|
for i := uint8(0); i < t.tabWidth; i++ {
|
||||||
|
t.doWrite(' ', true)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.doWrite(b, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// doWrite writes the specified character together with the current fg/bg
|
||||||
|
// attributes at the current data offset advancing the cursor position if
|
||||||
|
// advanceCursor is true. If the terminal is active, then doWrite also writes
|
||||||
|
// the character to the attached console.
|
||||||
|
func (t *VT) doWrite(b byte, advanceCursor bool) {
|
||||||
|
if t.state == StateActive {
|
||||||
|
t.cons.Write(b, t.curFg, t.curBg, t.cursorX, t.cursorY)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.data[t.dataOffset] = b
|
||||||
|
t.data[t.dataOffset+1] = t.curFg
|
||||||
|
t.data[t.dataOffset+2] = t.curBg
|
||||||
|
|
||||||
|
if advanceCursor {
|
||||||
|
// Advance x position and handle wrapping when the cursor reaches the
|
||||||
|
// end of the current line
|
||||||
|
t.dataOffset += 3
|
||||||
|
t.cursorX++
|
||||||
|
if t.cursorX > t.viewportWidth {
|
||||||
|
t.lf(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cr resets the x coordinate of the terminal cursor to 0.
|
||||||
|
func (t *VT) cr() {
|
||||||
|
t.cursorX = 1
|
||||||
|
t.updateDataOffset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// lf advances the y coordinate of the terminal cursor by one line scrolling
|
||||||
|
// the terminal contents if the end of the last terminal line is reached.
|
||||||
|
func (t *VT) lf(withCR bool) {
|
||||||
|
if withCR {
|
||||||
|
t.cursorX = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
// Cursor has not reached the end of the viewport
|
||||||
|
case t.cursorY+1 <= t.viewportHeight:
|
||||||
|
t.cursorY++
|
||||||
|
default:
|
||||||
|
// Check if the viewport can be scrolled down
|
||||||
|
if t.viewportY+t.viewportHeight < t.termHeight {
|
||||||
|
t.viewportY++
|
||||||
|
} else {
|
||||||
|
// We have reached the bottom of the terminal buffer.
|
||||||
|
// We need to scroll its contents up and clear the last line
|
||||||
|
var stride = int(t.viewportWidth * 3)
|
||||||
|
var startOffset = int(t.viewportY) * stride
|
||||||
|
var endOffset = int(t.viewportY+t.viewportHeight-1) * stride
|
||||||
|
|
||||||
|
for offset := startOffset; offset < endOffset; offset++ {
|
||||||
|
t.data[offset] = t.data[offset+stride]
|
||||||
|
}
|
||||||
|
|
||||||
|
for offset := endOffset; offset < endOffset+stride; offset += 3 {
|
||||||
|
t.data[offset+0] = ' '
|
||||||
|
t.data[offset+1] = t.defaultFg
|
||||||
|
t.data[offset+2] = t.defaultBg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync console
|
||||||
|
if t.state == StateActive {
|
||||||
|
t.cons.Scroll(console.ScrollDirUp, 1)
|
||||||
|
t.cons.Fill(1, t.cursorY, t.termWidth, 1, t.defaultFg, t.defaultBg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.updateDataOffset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateDataOffset calculates the offset in the data buffer taking into account
|
||||||
|
// the cursor position and the viewportY value.
|
||||||
|
func (t *VT) updateDataOffset() {
|
||||||
|
t.dataOffset = uint((t.viewportY+(t.cursorY-1))*(t.viewportWidth*3) + ((t.cursorX - 1) * 3))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DriverName returns the name of this driver.
|
||||||
|
func (t *VT) DriverName() string {
|
||||||
|
return "vt"
|
||||||
|
}
|
||||||
|
|
||||||
|
// DriverVersion returns the version of this driver.
|
||||||
|
func (t *VT) DriverVersion() (uint16, uint16, uint16) {
|
||||||
|
return 0, 0, 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// DriverInit initializes this driver.
|
||||||
|
func (t *VT) DriverInit() *kernel.Error { return nil }
|
||||||
|
|
||||||
|
func probeForVT() device.Driver {
|
||||||
|
return NewVT(DefaultTabWidth, DefaultScrollback)
|
||||||
|
}
|
430
src/gopheros/device/tty/vt_test.go
Normal file
430
src/gopheros/device/tty/vt_test.go
Normal file
@ -0,0 +1,430 @@
|
|||||||
|
package tty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gopheros/device"
|
||||||
|
"gopheros/device/video/console"
|
||||||
|
"image/color"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVtPosition(t *testing.T) {
|
||||||
|
specs := []struct {
|
||||||
|
inX, inY uint16
|
||||||
|
expX, expY uint16
|
||||||
|
}{
|
||||||
|
{20, 20, 20, 20},
|
||||||
|
{100, 20, 80, 20},
|
||||||
|
{10, 200, 10, 25},
|
||||||
|
{10, 200, 10, 25},
|
||||||
|
{100, 100, 80, 25},
|
||||||
|
}
|
||||||
|
|
||||||
|
term := NewVT(4, 0)
|
||||||
|
|
||||||
|
// SetCursorPosition without an attached console is a no-op
|
||||||
|
term.SetCursorPosition(2, 2)
|
||||||
|
|
||||||
|
if curX, curY := term.CursorPosition(); curX != 1 || curY != 1 {
|
||||||
|
t.Fatalf("expected terminal initial position to be (1, 1); got (%d, %d)", curX, curY)
|
||||||
|
}
|
||||||
|
|
||||||
|
cons := newMockConsole(80, 25)
|
||||||
|
term.AttachTo(cons)
|
||||||
|
|
||||||
|
for specIndex, spec := range specs {
|
||||||
|
term.SetCursorPosition(spec.inX, spec.inY)
|
||||||
|
if x, y := term.CursorPosition(); x != spec.expX || y != spec.expY {
|
||||||
|
t.Errorf("[spec %d] expected setting position to (%d, %d) to update the position to (%d, %d); got (%d, %d)", specIndex, spec.inX, spec.inY, spec.expX, spec.expY, x, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVtWrite(t *testing.T) {
|
||||||
|
t.Run("inactive terminal", func(t *testing.T) {
|
||||||
|
cons := newMockConsole(80, 25)
|
||||||
|
|
||||||
|
term := NewVT(4, 0)
|
||||||
|
if _, err := term.Write([]byte("foo")); err != io.ErrClosedPipe {
|
||||||
|
t.Fatal("expected calling Write on a terminal without an attached console to return ErrClosedPipe")
|
||||||
|
}
|
||||||
|
|
||||||
|
term.AttachTo(cons)
|
||||||
|
|
||||||
|
term.curFg = 2
|
||||||
|
term.curBg = 3
|
||||||
|
|
||||||
|
data := []byte("\b123\b4\t5\n67\r68")
|
||||||
|
count, err := term.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if count != len(data) {
|
||||||
|
t.Fatalf("expected to write %d bytes; wrote %d", len(data), count)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cons.bytesWritten != 0 {
|
||||||
|
t.Fatalf("expected writes not to be synced with console when terminal is inactive; %d bytes written", cons.bytesWritten)
|
||||||
|
}
|
||||||
|
|
||||||
|
specs := []struct {
|
||||||
|
x, y uint16
|
||||||
|
expByte uint8
|
||||||
|
}{
|
||||||
|
{1, 1, '1'},
|
||||||
|
{2, 1, '2'},
|
||||||
|
{3, 1, '4'},
|
||||||
|
{8, 1, '5'}, // 2 + tabWidth + 1
|
||||||
|
{1, 2, '6'},
|
||||||
|
{2, 2, '8'},
|
||||||
|
}
|
||||||
|
|
||||||
|
for specIndex, spec := range specs {
|
||||||
|
offset := ((spec.y - 1) * term.viewportWidth * 3) + ((spec.x - 1) * 3)
|
||||||
|
if term.data[offset] != spec.expByte {
|
||||||
|
t.Errorf("[spec %d] expected char at (%d, %d) to be %q; got %q", specIndex, spec.x, spec.y, spec.expByte, term.data[offset])
|
||||||
|
}
|
||||||
|
|
||||||
|
if term.data[offset+1] != term.curFg {
|
||||||
|
t.Errorf("[spec %d] expected fg attribute at (%d, %d) to be %d; got %d", specIndex, spec.x, spec.y, term.curFg, term.data[offset+1])
|
||||||
|
}
|
||||||
|
|
||||||
|
if term.data[offset+2] != term.curBg {
|
||||||
|
t.Errorf("[spec %d] expected bg attribute at (%d, %d) to be %d; got %d", specIndex, spec.x, spec.y, term.curBg, term.data[offset+2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("active terminal", func(t *testing.T) {
|
||||||
|
cons := newMockConsole(80, 25)
|
||||||
|
|
||||||
|
term := NewVT(4, 0)
|
||||||
|
term.SetState(StateActive)
|
||||||
|
term.SetState(StateActive) // calling SetState with the same state is a no-op
|
||||||
|
|
||||||
|
if got := term.State(); got != StateActive {
|
||||||
|
t.Fatalf("expected terminal state to be %d; got %d", StateActive, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
term.AttachTo(cons)
|
||||||
|
|
||||||
|
term.curFg = 2
|
||||||
|
term.curBg = 3
|
||||||
|
|
||||||
|
data := []byte("\b123\b4\t5\n67\r68")
|
||||||
|
term.Write(data)
|
||||||
|
|
||||||
|
if expCount := len(data); cons.bytesWritten != expCount {
|
||||||
|
t.Fatalf("expected writes to be synced with console when terminal is active. %d bytes written; expected %d", cons.bytesWritten, expCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
specs := []struct {
|
||||||
|
x, y uint16
|
||||||
|
expByte uint8
|
||||||
|
}{
|
||||||
|
{1, 1, '1'},
|
||||||
|
{2, 1, '2'},
|
||||||
|
{3, 1, '4'},
|
||||||
|
{8, 1, '5'}, // 2 + tabWidth + 1
|
||||||
|
{1, 2, '6'},
|
||||||
|
{2, 2, '8'},
|
||||||
|
}
|
||||||
|
|
||||||
|
for specIndex, spec := range specs {
|
||||||
|
offset := ((spec.y - 1) * cons.width) + (spec.x - 1)
|
||||||
|
if cons.chars[offset] != spec.expByte {
|
||||||
|
t.Errorf("[spec %d] expected console char at (%d, %d) to be %q; got %q", specIndex, spec.x, spec.y, spec.expByte, cons.chars[offset])
|
||||||
|
}
|
||||||
|
|
||||||
|
if cons.fgAttrs[offset] != term.curFg {
|
||||||
|
t.Errorf("[spec %d] expected console fg attribute at (%d, %d) to be %d; got %d", specIndex, spec.x, spec.y, term.curFg, cons.fgAttrs[offset])
|
||||||
|
}
|
||||||
|
|
||||||
|
if cons.bgAttrs[offset] != term.curBg {
|
||||||
|
t.Errorf("[spec %d] expected console bg attribute at (%d, %d) to be %d; got %d", specIndex, spec.x, spec.y, term.curBg, cons.bgAttrs[offset])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVtLineFeedHandling(t *testing.T) {
|
||||||
|
t.Run("viewport at end of terminal", func(t *testing.T) {
|
||||||
|
cons := newMockConsole(80, 25)
|
||||||
|
|
||||||
|
term := NewVT(4, 0)
|
||||||
|
term.SetState(StateActive)
|
||||||
|
term.AttachTo(cons)
|
||||||
|
|
||||||
|
// Fill last line except the last column which will trigger a
|
||||||
|
// line feed. Cursor position will be automatically clipped to
|
||||||
|
// the viewport bounds
|
||||||
|
term.SetCursorPosition(1, term.viewportHeight+1)
|
||||||
|
for i := uint16(0); i < term.viewportWidth-1; i++ {
|
||||||
|
term.WriteByte(byte('0' + (i % 10)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emulate viewportHeight line feeds. The last one should cause a scroll
|
||||||
|
term.SetCursorPosition(0, 0) // cursor is set to (1,1)
|
||||||
|
for i := uint16(0); i < term.viewportHeight; i++ {
|
||||||
|
term.lf(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cons.scrollUpCount != 1 {
|
||||||
|
t.Fatalf("expected console to be scrolled up 1 time; got %d", cons.scrollUpCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set cursor one line above the last; this line should now
|
||||||
|
// contain the scrolled contents
|
||||||
|
term.SetCursorPosition(1, term.viewportHeight-1)
|
||||||
|
for col, offset := uint16(1), term.dataOffset; col <= term.viewportWidth; col, offset = col+1, offset+3 {
|
||||||
|
expByte := byte('0' + ((col - 1) % 10))
|
||||||
|
if col == term.viewportWidth {
|
||||||
|
expByte = ' '
|
||||||
|
}
|
||||||
|
|
||||||
|
if term.data[offset] != expByte {
|
||||||
|
t.Errorf("expected char at (%d, %d) to be %q; got %q", col, term.viewportHeight-1, expByte, term.data[offset])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set cursor to the last line. This line should now be cleared
|
||||||
|
term.SetCursorPosition(1, term.viewportHeight)
|
||||||
|
for col, offset := uint16(1), term.dataOffset; col <= term.viewportWidth; col, offset = col+1, offset+3 {
|
||||||
|
expByte := uint8(' ')
|
||||||
|
if term.data[offset] != expByte {
|
||||||
|
t.Errorf("expected char at (%d, %d) to be %q; got %q", col, term.viewportHeight, expByte, term.data[offset])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("viewport not at end of terminal", func(t *testing.T) {
|
||||||
|
cons := newMockConsole(80, 25)
|
||||||
|
|
||||||
|
term := NewVT(4, 1)
|
||||||
|
term.SetState(StateActive)
|
||||||
|
term.AttachTo(cons)
|
||||||
|
|
||||||
|
// Fill last line except the last column which will trigger a
|
||||||
|
// line feed. Cursor position will be automatically clipped to
|
||||||
|
// the viewport bounds
|
||||||
|
term.SetCursorPosition(1, term.viewportHeight+1)
|
||||||
|
for i := uint16(0); i < term.viewportWidth-1; i++ {
|
||||||
|
term.WriteByte(byte('0' + (i % 10)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill first line including the last column
|
||||||
|
term.SetCursorPosition(1, 1)
|
||||||
|
for i := uint16(0); i < term.viewportWidth; i++ {
|
||||||
|
term.WriteByte(byte('0' + (i % 10)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emulate viewportHeight line feeds. The last one should cause a scroll
|
||||||
|
// in the console but only a viewport adjustment in the terminal
|
||||||
|
term.SetCursorPosition(0, 0) // cursor is set to (1,1)
|
||||||
|
for i := uint16(0); i < term.viewportHeight; i++ {
|
||||||
|
term.lf(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cons.scrollUpCount != 1 {
|
||||||
|
t.Fatalf("expected console to be scrolled up 1 time; got %d", cons.scrollUpCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if expViewportY := uint16(1); term.viewportY != expViewportY {
|
||||||
|
t.Fatalf("expected terminal viewportY to be adjusted to %d; got %d", expViewportY, term.viewportY)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that first line is still available in the terminal buffer
|
||||||
|
// that is not currently visible
|
||||||
|
term.SetCursorPosition(1, 1)
|
||||||
|
offset := term.dataOffset - uint(term.viewportWidth*3)
|
||||||
|
|
||||||
|
for col := uint16(1); col <= term.viewportWidth; col, offset = col+1, offset+3 {
|
||||||
|
expByte := byte('0' + ((col - 1) % 10))
|
||||||
|
|
||||||
|
if term.data[offset] != expByte {
|
||||||
|
t.Errorf("expected char at hidden region (%d, -1) to be %q; got %q", col, expByte, term.data[offset])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVtAttach(t *testing.T) {
|
||||||
|
cons := newMockConsole(80, 25)
|
||||||
|
|
||||||
|
term := NewVT(4, 1)
|
||||||
|
|
||||||
|
// AttachTo with a nil console should be a no-op
|
||||||
|
term.AttachTo(nil)
|
||||||
|
if term.termWidth != 0 || term.termHeight != 0 || term.viewportWidth != 0 || term.viewportHeight != 0 {
|
||||||
|
t.Fatal("expected attaching a nil console to be a no-op")
|
||||||
|
}
|
||||||
|
|
||||||
|
term.AttachTo(cons)
|
||||||
|
if term.termWidth != cons.width ||
|
||||||
|
term.termHeight != cons.height+term.scrollback ||
|
||||||
|
term.viewportWidth != cons.width ||
|
||||||
|
term.viewportHeight != cons.height ||
|
||||||
|
term.data == nil {
|
||||||
|
t.Fatal("expected the terminal to initialize using the attached console info")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVtSetState(t *testing.T) {
|
||||||
|
cons := newMockConsole(80, 25)
|
||||||
|
term := NewVT(4, 1)
|
||||||
|
term.AttachTo(cons)
|
||||||
|
|
||||||
|
// Fill terminal viewport using a rotating pattern. Writing the last
|
||||||
|
// character will cause a scroll operation moving the viewport down
|
||||||
|
row := 0
|
||||||
|
for index := 0; index < int(term.viewportWidth*term.viewportHeight); index++ {
|
||||||
|
if index != 0 && index%int(term.viewportWidth) == 0 {
|
||||||
|
row++
|
||||||
|
}
|
||||||
|
term.curFg = uint8((row + index + 1) % 10)
|
||||||
|
term.curBg = uint8((row + index + 2) % 10)
|
||||||
|
term.WriteByte(byte('0' + (row+index)%10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activating this terminal should trigger a copy of the terminal viewport
|
||||||
|
// contents to the console.
|
||||||
|
term.SetState(StateActive)
|
||||||
|
row = 1
|
||||||
|
for index := 0; index < len(cons.chars); index++ {
|
||||||
|
if index != 0 && index%int(cons.width) == 0 {
|
||||||
|
row++
|
||||||
|
}
|
||||||
|
|
||||||
|
expCh := uint8('0' + (row+index)%10)
|
||||||
|
expFg := uint8((row + index + 1) % 10)
|
||||||
|
expBg := uint8((row + index + 2) % 10)
|
||||||
|
|
||||||
|
// last line should be cleared due to the scroll operation
|
||||||
|
if row == int(cons.height) {
|
||||||
|
expCh = ' '
|
||||||
|
expFg = 7
|
||||||
|
expBg = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if cons.chars[index] != expCh {
|
||||||
|
t.Errorf("expected console char at index %d to be %q; got %q", index, expCh, cons.chars[index])
|
||||||
|
}
|
||||||
|
|
||||||
|
if cons.fgAttrs[index] != expFg {
|
||||||
|
t.Errorf("expected console fg attr at index %d to be %d; got %d", index, expFg, cons.fgAttrs[index])
|
||||||
|
}
|
||||||
|
|
||||||
|
if cons.bgAttrs[index] != expBg {
|
||||||
|
t.Errorf("expected console bg attr at index %d to be %d; got %d", index, expBg, cons.bgAttrs[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVTDriverInterface(t *testing.T) {
|
||||||
|
var dev device.Driver = NewVT(0, 0)
|
||||||
|
|
||||||
|
if err := dev.DriverInit(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVTProbe(t *testing.T) {
|
||||||
|
var (
|
||||||
|
expProbePtr = reflect.ValueOf(probeForVT).Pointer()
|
||||||
|
foundProbe bool
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, probeFn := range HWProbes() {
|
||||||
|
if reflect.ValueOf(probeFn).Pointer() == expProbePtr {
|
||||||
|
foundProbe = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundProbe {
|
||||||
|
t.Fatal("expected probeForVT to be part of the probes returned by HWProbes")
|
||||||
|
}
|
||||||
|
|
||||||
|
if drv := probeForVT(); drv == nil {
|
||||||
|
t.Fatal("expected probeForVT to return a driver")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockConsole struct {
|
||||||
|
width, height uint16
|
||||||
|
fg, bg uint8
|
||||||
|
chars []uint8
|
||||||
|
fgAttrs []uint8
|
||||||
|
bgAttrs []uint8
|
||||||
|
bytesWritten int
|
||||||
|
scrollUpCount int
|
||||||
|
scrollDownCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockConsole(w, h uint16) *mockConsole {
|
||||||
|
return &mockConsole{
|
||||||
|
width: w,
|
||||||
|
height: h,
|
||||||
|
fg: 7,
|
||||||
|
bg: 0,
|
||||||
|
chars: make([]uint8, w*h),
|
||||||
|
fgAttrs: make([]uint8, w*h),
|
||||||
|
bgAttrs: make([]uint8, w*h),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cons *mockConsole) Dimensions() (uint16, uint16) {
|
||||||
|
return cons.width, cons.height
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cons *mockConsole) DefaultColors() (uint8, uint8) {
|
||||||
|
return cons.fg, cons.bg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cons *mockConsole) Fill(x, y, width, height uint16, fg, bg uint8) {
|
||||||
|
yEnd := y + height - 1
|
||||||
|
xEnd := x + width - 1
|
||||||
|
|
||||||
|
for fy := y; fy <= yEnd; fy++ {
|
||||||
|
offset := ((fy - 1) * cons.width)
|
||||||
|
for fx := x; fx <= xEnd; fx, offset = fx+1, offset+1 {
|
||||||
|
cons.chars[offset] = ' '
|
||||||
|
cons.fgAttrs[offset] = fg
|
||||||
|
cons.bgAttrs[offset] = bg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cons *mockConsole) Scroll(dir console.ScrollDir, lines uint16) {
|
||||||
|
switch dir {
|
||||||
|
case console.ScrollDirUp:
|
||||||
|
cons.scrollUpCount++
|
||||||
|
case console.ScrollDirDown:
|
||||||
|
cons.scrollDownCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cons *mockConsole) Palette() color.Palette {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cons *mockConsole) SetPaletteColor(index uint8, color color.RGBA) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cons *mockConsole) Write(b byte, fg, bg uint8, x, y uint16) {
|
||||||
|
offset := ((y - 1) * cons.width) + (x - 1)
|
||||||
|
cons.chars[offset] = b
|
||||||
|
cons.fgAttrs[offset] = fg
|
||||||
|
cons.bgAttrs[offset] = bg
|
||||||
|
cons.bytesWritten++
|
||||||
|
}
|
45
src/gopheros/device/video/console/device.go
Normal file
45
src/gopheros/device/video/console/device.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package console
|
||||||
|
|
||||||
|
import "image/color"
|
||||||
|
|
||||||
|
// ScrollDir defines a scroll direction.
|
||||||
|
type ScrollDir uint8
|
||||||
|
|
||||||
|
// The supported list of scroll directions for the console Scroll() calls.
|
||||||
|
const (
|
||||||
|
ScrollDirUp ScrollDir = iota
|
||||||
|
ScrollDirDown
|
||||||
|
)
|
||||||
|
|
||||||
|
// The Device interface is implemented by objects that can function as system
|
||||||
|
// consoles.
|
||||||
|
type Device interface {
|
||||||
|
// Dimensions returns the width and height of the console in characters.
|
||||||
|
Dimensions() (uint16, uint16)
|
||||||
|
|
||||||
|
// DefaultColors returns the default foreground and background colors
|
||||||
|
// used by this console.
|
||||||
|
DefaultColors() (fg, bg uint8)
|
||||||
|
|
||||||
|
// Fill sets the contents of the specified rectangular region to the
|
||||||
|
// requested color. Both x and y coordinates are 1-based (top-left
|
||||||
|
// corner has coordinates 1,1).
|
||||||
|
Fill(x, y, width, height uint16, fg, bg uint8)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
Scroll(dir ScrollDir, lines uint16)
|
||||||
|
|
||||||
|
// Write a char to the specified location. Both x and y coordinates are
|
||||||
|
// 1-based (top-left corner has coordinates 1,1).
|
||||||
|
Write(ch byte, fg, bg uint8, x, y uint16)
|
||||||
|
|
||||||
|
// Palette returns the active color palette for this console.
|
||||||
|
Palette() color.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.
|
||||||
|
SetPaletteColor(uint8, color.RGBA)
|
||||||
|
}
|
16
src/gopheros/device/video/console/probe.go
Normal file
16
src/gopheros/device/video/console/probe.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package console
|
||||||
|
|
||||||
|
import "gopheros/device"
|
||||||
|
import "gopheros/kernel/hal/multiboot"
|
||||||
|
|
||||||
|
var (
|
||||||
|
getFramebufferInfoFn = multiboot.GetFramebufferInfo
|
||||||
|
)
|
||||||
|
|
||||||
|
// HWProbes returns a slice of device.ProbeFn that can be used by the hal
|
||||||
|
// package to probe for console device hardware.
|
||||||
|
func HWProbes() []device.ProbeFn {
|
||||||
|
return []device.ProbeFn{
|
||||||
|
probeForVgaTextConsole,
|
||||||
|
}
|
||||||
|
}
|
212
src/gopheros/device/video/console/vga_text.go
Normal file
212
src/gopheros/device/video/console/vga_text.go
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gopheros/device"
|
||||||
|
"gopheros/kernel"
|
||||||
|
"gopheros/kernel/cpu"
|
||||||
|
"gopheros/kernel/hal/multiboot"
|
||||||
|
"image/color"
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var portWriteByteFn = cpu.PortWriteByte
|
||||||
|
|
||||||
|
// VgaTextConsole implements an EGA-compatible 80x25 text console using VGA
|
||||||
|
// mode 0x3. The console supports the default 16 EGA colors which can be
|
||||||
|
// overridden using the SetPaletteColor method.
|
||||||
|
//
|
||||||
|
// Each character in the console framebuffer is represented using two bytes,
|
||||||
|
// a byte for the character ASCII code and a byte that encodes the foreground
|
||||||
|
// and background colors (4 bits for each).
|
||||||
|
//
|
||||||
|
// The default settings for the console are:
|
||||||
|
// - light gray text (color 7) on black background (color 0).
|
||||||
|
// - space as the clear character
|
||||||
|
type VgaTextConsole struct {
|
||||||
|
width uint16
|
||||||
|
height uint16
|
||||||
|
|
||||||
|
fb []uint16
|
||||||
|
|
||||||
|
palette color.Palette
|
||||||
|
defaultFg uint8
|
||||||
|
defaultBg uint8
|
||||||
|
clearChar uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVgaTextConsole creates an new vga text console with its
|
||||||
|
// framebuffer mapped to fbPhysAddr.
|
||||||
|
func NewVgaTextConsole(columns, rows uint16, fbPhysAddr uintptr) *VgaTextConsole {
|
||||||
|
return &VgaTextConsole{
|
||||||
|
width: columns,
|
||||||
|
height: rows,
|
||||||
|
clearChar: uint16(' '),
|
||||||
|
// overlay a 16bit slice over the fbPhysAddr
|
||||||
|
fb: *(*[]uint16)(unsafe.Pointer(&reflect.SliceHeader{
|
||||||
|
Len: 80 * 25,
|
||||||
|
Cap: 80 * 25,
|
||||||
|
Data: fbPhysAddr,
|
||||||
|
})),
|
||||||
|
palette: color.Palette{
|
||||||
|
color.RGBA{R: 0, G: 0, B: 1}, /* 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 */
|
||||||
|
},
|
||||||
|
// light gray text on black background
|
||||||
|
defaultFg: 7,
|
||||||
|
defaultBg: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dimensions returns the console width and height in characters.
|
||||||
|
func (cons *VgaTextConsole) Dimensions() (uint16, uint16) {
|
||||||
|
return cons.width, cons.height
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultColors returns the default foreground and background colors
|
||||||
|
// used by this console.
|
||||||
|
func (cons *VgaTextConsole) 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 *VgaTextConsole) Fill(x, y, width, height uint16, fg, bg uint8) {
|
||||||
|
var (
|
||||||
|
clr = (((uint16(bg) << 4) | uint16(fg)) << 8) | cons.clearChar
|
||||||
|
rowOffset, colOffset uint16
|
||||||
|
)
|
||||||
|
|
||||||
|
// clip rectangle
|
||||||
|
if x == 0 {
|
||||||
|
x = 1
|
||||||
|
} else if x >= cons.width {
|
||||||
|
x = cons.width
|
||||||
|
}
|
||||||
|
|
||||||
|
if y == 0 {
|
||||||
|
y = 1
|
||||||
|
} else 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 - 1) * cons.width) + (x - 1)
|
||||||
|
for ; height > 0; height, rowOffset = height-1, rowOffset+cons.width {
|
||||||
|
for colOffset = rowOffset; colOffset < rowOffset+width; colOffset++ {
|
||||||
|
cons.fb[colOffset] = clr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 *VgaTextConsole) Scroll(dir ScrollDir, lines uint16) {
|
||||||
|
if lines == 0 || lines > cons.height {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var i uint16
|
||||||
|
offset := lines * cons.width
|
||||||
|
|
||||||
|
switch dir {
|
||||||
|
case ScrollDirUp:
|
||||||
|
for ; i < (cons.height-lines)*cons.width; i++ {
|
||||||
|
cons.fb[i] = cons.fb[i+offset]
|
||||||
|
}
|
||||||
|
case ScrollDirDown:
|
||||||
|
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. 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 *VgaTextConsole) Write(ch byte, fg, bg uint8, x, y uint16) {
|
||||||
|
if x < 1 || x > cons.width || y < 1 || y > cons.height {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
maxColorIndex := uint8(len(cons.palette) - 1)
|
||||||
|
if fg > maxColorIndex {
|
||||||
|
fg = cons.defaultFg
|
||||||
|
}
|
||||||
|
if bg >= maxColorIndex {
|
||||||
|
bg = cons.defaultBg
|
||||||
|
}
|
||||||
|
|
||||||
|
cons.fb[((y-1)*cons.width)+(x-1)] = (((uint16(bg) << 4) | uint16(fg)) << 8) | uint16(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Palette returns the active color palette for this console.
|
||||||
|
func (cons *VgaTextConsole) 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 *VgaTextConsole) SetPaletteColor(index uint8, rgba color.RGBA) {
|
||||||
|
if index >= uint8(len(cons.palette)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cons.palette[index] = rgba
|
||||||
|
|
||||||
|
// Load palette entry to the DAC. In this mode, colors are specified
|
||||||
|
// using 6-bits for each component; the RGB values need to be converted
|
||||||
|
// to the 0-63 range.
|
||||||
|
portWriteByteFn(0x3c8, index)
|
||||||
|
portWriteByteFn(0x3c9, rgba.R>>2)
|
||||||
|
portWriteByteFn(0x3c9, rgba.G>>2)
|
||||||
|
portWriteByteFn(0x3c9, rgba.B>>2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DriverName returns the name of this driver.
|
||||||
|
func (cons *VgaTextConsole) DriverName() string {
|
||||||
|
return "vga_text_console"
|
||||||
|
}
|
||||||
|
|
||||||
|
// DriverVersion returns the version of this driver.
|
||||||
|
func (cons *VgaTextConsole) DriverVersion() (uint16, uint16, uint16) {
|
||||||
|
return 0, 0, 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// DriverInit initializes this driver.
|
||||||
|
func (cons *VgaTextConsole) DriverInit() *kernel.Error { return nil }
|
||||||
|
|
||||||
|
// probeForVgaTextConsole checks for the presence of a vga text console.
|
||||||
|
func probeForVgaTextConsole() device.Driver {
|
||||||
|
var drv device.Driver
|
||||||
|
|
||||||
|
fbInfo := getFramebufferInfoFn()
|
||||||
|
if fbInfo.Type == multiboot.FramebufferTypeEGA {
|
||||||
|
drv = NewVgaTextConsole(uint16(fbInfo.Width), uint16(fbInfo.Height), uintptr(fbInfo.PhysAddr))
|
||||||
|
}
|
||||||
|
|
||||||
|
return drv
|
||||||
|
}
|
358
src/gopheros/device/video/console/vga_text_test.go
Normal file
358
src/gopheros/device/video/console/vga_text_test.go
Normal file
@ -0,0 +1,358 @@
|
|||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gopheros/device"
|
||||||
|
"gopheros/kernel/cpu"
|
||||||
|
"gopheros/kernel/hal/multiboot"
|
||||||
|
"image/color"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVgaTextDimensions(t *testing.T) {
|
||||||
|
cons := NewVgaTextConsole(80, 25, 0)
|
||||||
|
if w, h := cons.Dimensions(); w != 80 || h != 25 {
|
||||||
|
t.Fatalf("expected console dimensions to be 80x25; got %dx%d", w, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVgaTextDefaultColors(t *testing.T) {
|
||||||
|
cons := NewVgaTextConsole(80, 25, 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 TestVgaTextFill(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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fb := make([]uint16, 80*25)
|
||||||
|
cons := NewVgaTextConsole(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
||||||
|
cw, ch := cons.Dimensions()
|
||||||
|
|
||||||
|
testPat := uint16(0xDEAD)
|
||||||
|
clearPat := uint16(cons.clearChar)
|
||||||
|
|
||||||
|
nextSpec:
|
||||||
|
for specIndex, spec := range specs {
|
||||||
|
// Fill FB with test pattern
|
||||||
|
for i := 0; i < len(fb); i++ {
|
||||||
|
fb[i] = testPat
|
||||||
|
}
|
||||||
|
|
||||||
|
cons.Fill(spec.x, spec.y, spec.w, spec.h, 0, 0)
|
||||||
|
|
||||||
|
var x, y uint16
|
||||||
|
for y = 1; y <= ch; y++ {
|
||||||
|
for x = 1; x <= cw; x++ {
|
||||||
|
fbVal := fb[((y-1)*cw)+(x-1)]
|
||||||
|
|
||||||
|
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 TestVgaTextScroll(t *testing.T) {
|
||||||
|
fb := make([]uint16, 80*25)
|
||||||
|
cons := NewVgaTextConsole(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
||||||
|
cw, ch := cons.Dimensions()
|
||||||
|
|
||||||
|
t.Run("up", func(t *testing.T) {
|
||||||
|
specs := []uint16{
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
}
|
||||||
|
nextSpec:
|
||||||
|
for specIndex, lines := range specs {
|
||||||
|
// Fill buffer with test pattern
|
||||||
|
var x, y, index uint16
|
||||||
|
for y = 0; y < ch; y++ {
|
||||||
|
for x = 0; x < cw; x++ {
|
||||||
|
fb[index] = (y << 8) | x
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cons.Scroll(ScrollDirUp, lines)
|
||||||
|
|
||||||
|
// Check that rows 1 to (height - lines) have been scrolled up
|
||||||
|
index = 0
|
||||||
|
for y = 0; y < ch-lines; y++ {
|
||||||
|
for x = 0; x < cw; x++ {
|
||||||
|
expVal := ((y + lines) << 8) | x
|
||||||
|
if fb[index] != expVal {
|
||||||
|
t.Errorf("[spec %d] expected value at (%d, %d) to be %d; got %d", specIndex, x, y, expVal, fb[index])
|
||||||
|
continue nextSpec
|
||||||
|
}
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("down", func(t *testing.T) {
|
||||||
|
specs := []uint16{
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
}
|
||||||
|
|
||||||
|
nextSpec:
|
||||||
|
for specIndex, lines := range specs {
|
||||||
|
// Fill buffer with test pattern
|
||||||
|
var x, y, index uint16
|
||||||
|
for y = 0; y < ch; y++ {
|
||||||
|
for x = 0; x < cw; x++ {
|
||||||
|
fb[index] = (y << 8) | x
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cons.Scroll(ScrollDirDown, lines)
|
||||||
|
|
||||||
|
// Check that rows lines to height have been scrolled down
|
||||||
|
index = lines * cw
|
||||||
|
for y = lines; y < ch-lines; y++ {
|
||||||
|
for x = 0; x < cw; x++ {
|
||||||
|
expVal := ((y - lines) << 8) | x
|
||||||
|
if fb[index] != expVal {
|
||||||
|
t.Errorf("[spec %d] expected value at (%d, %d) to be %d; got %d", specIndex, x, y, expVal, fb[index])
|
||||||
|
continue nextSpec
|
||||||
|
}
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVgaTextWrite(t *testing.T) {
|
||||||
|
fb := make([]uint16, 80*25)
|
||||||
|
cons := NewVgaTextConsole(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
||||||
|
defaultFg, defaultBg := cons.DefaultColors()
|
||||||
|
|
||||||
|
t.Run("off-screen", func(t *testing.T) {
|
||||||
|
specs := []struct {
|
||||||
|
x, y uint16
|
||||||
|
}{
|
||||||
|
{81, 26},
|
||||||
|
{90, 24},
|
||||||
|
{79, 30},
|
||||||
|
{100, 100},
|
||||||
|
}
|
||||||
|
|
||||||
|
nextSpec:
|
||||||
|
for specIndex, spec := range specs {
|
||||||
|
for i := 0; i < len(fb); i++ {
|
||||||
|
fb[i] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
cons.Write('!', 1, 2, spec.x, spec.y)
|
||||||
|
|
||||||
|
for i := 0; i < len(fb); i++ {
|
||||||
|
if got := fb[i]; got != 0 {
|
||||||
|
t.Errorf("[spec %d] expected Write() with off-screen coords to be a no-op", specIndex)
|
||||||
|
continue nextSpec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
for i := 0; i < len(fb); i++ {
|
||||||
|
fb[i] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fg := uint8(1)
|
||||||
|
bg := uint8(2)
|
||||||
|
expAttr := uint16((uint16(bg) << 4) | uint16(fg))
|
||||||
|
|
||||||
|
cons.Write('!', fg, bg, 1, 1)
|
||||||
|
|
||||||
|
expVal := (expAttr << 8) | uint16('!')
|
||||||
|
if got := fb[0]; got != expVal {
|
||||||
|
t.Errorf("expected call to Write() to set fb[0] to %d; got %d", expVal, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("fg out of range", func(t *testing.T) {
|
||||||
|
for i := 0; i < len(fb); i++ {
|
||||||
|
fb[i] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fg := uint8(128)
|
||||||
|
bg := uint8(2)
|
||||||
|
expAttr := uint16((uint16(bg) << 4) | uint16(defaultFg))
|
||||||
|
|
||||||
|
cons.Write('!', fg, bg, 1, 1)
|
||||||
|
|
||||||
|
expVal := (expAttr << 8) | uint16('!')
|
||||||
|
if got := fb[0]; got != expVal {
|
||||||
|
t.Errorf("expected call to Write() to set fb[0] to %d; got %d", expVal, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bg out of range", func(t *testing.T) {
|
||||||
|
for i := 0; i < len(fb); i++ {
|
||||||
|
fb[i] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fg := uint8(8)
|
||||||
|
bg := uint8(255)
|
||||||
|
expAttr := uint16((uint16(defaultBg) << 4) | uint16(fg))
|
||||||
|
|
||||||
|
cons.Write('!', fg, bg, 1, 1)
|
||||||
|
|
||||||
|
expVal := (expAttr << 8) | uint16('!')
|
||||||
|
if got := fb[0]; got != expVal {
|
||||||
|
t.Errorf("expected call to Write() to set fb[0] to %d; got %d", expVal, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVgaTextSetPaletteColor(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
portWriteByteFn = cpu.PortWriteByte
|
||||||
|
}()
|
||||||
|
|
||||||
|
cons := NewVgaTextConsole(80, 25, 0)
|
||||||
|
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
expWrites := []struct {
|
||||||
|
port uint16
|
||||||
|
val uint8
|
||||||
|
}{
|
||||||
|
// Values will be normalized in the 0-31 range
|
||||||
|
{0x3c8, 1},
|
||||||
|
{0x3c9, 63},
|
||||||
|
{0x3c9, 31},
|
||||||
|
{0x3c9, 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCallCount := 0
|
||||||
|
portWriteByteFn = func(port uint16, val uint8) {
|
||||||
|
exp := expWrites[writeCallCount]
|
||||||
|
if port != exp.port || val != exp.val {
|
||||||
|
t.Errorf("[port write %d] expected port: 0x%x, val: %d; got port: 0x%x, val: %d", writeCallCount, exp.port, exp.val, port, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCallCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
rgba := color.RGBA{R: 255, G: 127, B: 0}
|
||||||
|
cons.SetPaletteColor(1, rgba)
|
||||||
|
|
||||||
|
if got := cons.Palette()[1]; got != rgba {
|
||||||
|
t.Errorf("expected color at index 1 to be:\n%v\ngot:\n%v", rgba, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if writeCallCount != len(expWrites) {
|
||||||
|
t.Errorf("expected cpu.portWriteByty to be called %d times; got %d", len(expWrites), writeCallCount)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("color index out of range", func(t *testing.T) {
|
||||||
|
portWriteByteFn = func(_ uint16, _ uint8) {
|
||||||
|
t.Error("unexpected call to cpu.PortWriteByte")
|
||||||
|
}
|
||||||
|
|
||||||
|
rgba := color.RGBA{R: 255, G: 127, B: 0}
|
||||||
|
cons.SetPaletteColor(50, rgba)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVgaTextDriverInterface(t *testing.T) {
|
||||||
|
var dev device.Driver = NewVgaTextConsole(80, 25, 0)
|
||||||
|
|
||||||
|
if err := dev.DriverInit(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVgaTextProbe(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
getFramebufferInfoFn = multiboot.GetFramebufferInfo
|
||||||
|
}()
|
||||||
|
|
||||||
|
var (
|
||||||
|
expProbePtr = reflect.ValueOf(probeForVgaTextConsole).Pointer()
|
||||||
|
foundProbe bool
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, probeFn := range HWProbes() {
|
||||||
|
if reflect.ValueOf(probeFn).Pointer() == expProbePtr {
|
||||||
|
foundProbe = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundProbe {
|
||||||
|
t.Fatal("expected probeForVgaTextConsole to be part of the probes returned by HWProbes")
|
||||||
|
}
|
||||||
|
|
||||||
|
getFramebufferInfoFn = func() *multiboot.FramebufferInfo {
|
||||||
|
return &multiboot.FramebufferInfo{
|
||||||
|
Width: 80,
|
||||||
|
Height: 25,
|
||||||
|
Pitch: 160,
|
||||||
|
PhysAddr: 0xb80000,
|
||||||
|
Type: multiboot.FramebufferTypeEGA,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if drv := probeForVgaTextConsole(); drv == nil {
|
||||||
|
t.Fatal("expected probeForVgaTextConsole to return a driver")
|
||||||
|
}
|
||||||
|
}
|
@ -1,19 +0,0 @@
|
|||||||
package tty
|
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
// Tty is implemented by objects that can register themselves as ttys.
|
|
||||||
type Tty interface {
|
|
||||||
io.Writer
|
|
||||||
io.ByteWriter
|
|
||||||
|
|
||||||
// Position returns the current cursor position (x, y).
|
|
||||||
Position() (uint16, uint16)
|
|
||||||
|
|
||||||
// SetPosition sets the current cursor position to (x,y). Console implementations
|
|
||||||
// must clip the provided cursor position if it exceeds the console dimensions.
|
|
||||||
SetPosition(x, y uint16)
|
|
||||||
|
|
||||||
// Clear clears the terminal.
|
|
||||||
Clear()
|
|
||||||
}
|
|
@ -1,128 +0,0 @@
|
|||||||
package tty
|
|
||||||
|
|
||||||
import "gopheros/kernel/driver/video/console"
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultFg = console.LightGrey
|
|
||||||
defaultBg = console.Black
|
|
||||||
tabWidth = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
// Vt implements a simple terminal that can process LF and CR characters. The
|
|
||||||
// terminal uses a console device for its output.
|
|
||||||
type Vt struct {
|
|
||||||
// Go interfaces will not work before we can get memory allocation working.
|
|
||||||
// Till then we need to use concrete types instead.
|
|
||||||
cons *console.Ega
|
|
||||||
|
|
||||||
width uint16
|
|
||||||
height uint16
|
|
||||||
|
|
||||||
curX uint16
|
|
||||||
curY uint16
|
|
||||||
curAttr console.Attr
|
|
||||||
}
|
|
||||||
|
|
||||||
// AttachTo links the terminal with the specified console device and updates
|
|
||||||
// the terminal's dimensions to match the ones reported by the attached device.
|
|
||||||
func (t *Vt) AttachTo(cons *console.Ega) {
|
|
||||||
t.cons = cons
|
|
||||||
t.width, t.height = cons.Dimensions()
|
|
||||||
t.curX = 0
|
|
||||||
t.curY = 0
|
|
||||||
|
|
||||||
// Default to lightgrey on black text.
|
|
||||||
t.curAttr = makeAttr(defaultFg, defaultBg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear clears the terminal.
|
|
||||||
func (t *Vt) Clear() {
|
|
||||||
t.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Position returns the current cursor position (x, y).
|
|
||||||
func (t *Vt) Position() (uint16, uint16) {
|
|
||||||
return t.curX, t.curY
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPosition sets the current cursor position to (x,y).
|
|
||||||
func (t *Vt) SetPosition(x, y uint16) {
|
|
||||||
if x >= t.width {
|
|
||||||
x = t.width - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if y >= t.height {
|
|
||||||
y = t.height - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
t.curX, t.curY = x, y
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write implements io.Writer.
|
|
||||||
func (t *Vt) Write(data []byte) (int, error) {
|
|
||||||
for _, b := range data {
|
|
||||||
t.WriteByte(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(data), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteByte implements io.ByteWriter.
|
|
||||||
func (t *Vt) WriteByte(b byte) error {
|
|
||||||
switch b {
|
|
||||||
case '\r':
|
|
||||||
t.cr()
|
|
||||||
case '\n':
|
|
||||||
t.cr()
|
|
||||||
t.lf()
|
|
||||||
case '\b':
|
|
||||||
if t.curX > 0 {
|
|
||||||
t.cons.Write(' ', t.curAttr, t.curX, t.curY)
|
|
||||||
t.curX--
|
|
||||||
}
|
|
||||||
case '\t':
|
|
||||||
for i := 0; i < tabWidth; i++ {
|
|
||||||
t.cons.Write(' ', t.curAttr, t.curX, t.curY)
|
|
||||||
t.curX++
|
|
||||||
if t.curX == t.width {
|
|
||||||
t.cr()
|
|
||||||
t.lf()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
t.cons.Write(b, t.curAttr, t.curX, t.curY)
|
|
||||||
t.curX++
|
|
||||||
if t.curX == t.width {
|
|
||||||
t.cr()
|
|
||||||
t.lf()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cls clears the terminal.
|
|
||||||
func (t *Vt) clear() {
|
|
||||||
t.cons.Clear(0, 0, t.width, t.height)
|
|
||||||
}
|
|
||||||
|
|
||||||
// cr resets the x coordinate of the terminal cursor to 0.
|
|
||||||
func (t *Vt) cr() {
|
|
||||||
t.curX = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// lf advances the y coordinate of the terminal cursor by one line scrolling
|
|
||||||
// the terminal contents if the end of the last terminal line is reached.
|
|
||||||
func (t *Vt) lf() {
|
|
||||||
if t.curY+1 < t.height {
|
|
||||||
t.curY++
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t.cons.Scroll(console.Up, 1)
|
|
||||||
t.cons.Clear(0, t.height-1, t.width, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeAttr(fg, bg console.Attr) console.Attr {
|
|
||||||
return (bg << 4) | (fg & 0xF)
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
package tty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gopheros/kernel/driver/video/console"
|
|
||||||
"testing"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestVtPosition(t *testing.T) {
|
|
||||||
specs := []struct {
|
|
||||||
inX, inY uint16
|
|
||||||
expX, expY uint16
|
|
||||||
}{
|
|
||||||
{20, 20, 20, 20},
|
|
||||||
{100, 20, 79, 20},
|
|
||||||
{10, 200, 10, 24},
|
|
||||||
{10, 200, 10, 24},
|
|
||||||
{100, 100, 79, 24},
|
|
||||||
}
|
|
||||||
|
|
||||||
fb := make([]uint16, 80*25)
|
|
||||||
var cons console.Ega
|
|
||||||
cons.Init(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
|
||||||
|
|
||||||
var vt Vt
|
|
||||||
vt.AttachTo(&cons)
|
|
||||||
|
|
||||||
for specIndex, spec := range specs {
|
|
||||||
vt.SetPosition(spec.inX, spec.inY)
|
|
||||||
if x, y := vt.Position(); x != spec.expX || y != spec.expY {
|
|
||||||
t.Errorf("[spec %d] expected setting position to (%d, %d) to update the position to (%d, %d); got (%d, %d)", specIndex, spec.inX, spec.inY, spec.expX, spec.expY, x, y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWrite(t *testing.T) {
|
|
||||||
fb := make([]uint16, 80*25)
|
|
||||||
var cons console.Ega
|
|
||||||
cons.Init(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
|
||||||
|
|
||||||
var vt Vt
|
|
||||||
vt.AttachTo(&cons)
|
|
||||||
|
|
||||||
vt.Clear()
|
|
||||||
vt.SetPosition(0, 1)
|
|
||||||
vt.Write([]byte("12\n\t3\n4\r567\b8"))
|
|
||||||
|
|
||||||
// Tab spanning rows
|
|
||||||
vt.SetPosition(78, 4)
|
|
||||||
vt.WriteByte('\t')
|
|
||||||
vt.WriteByte('9')
|
|
||||||
|
|
||||||
// Trigger scroll
|
|
||||||
vt.SetPosition(79, 24)
|
|
||||||
vt.Write([]byte{'!'})
|
|
||||||
|
|
||||||
specs := []struct {
|
|
||||||
x, y uint16
|
|
||||||
expChar byte
|
|
||||||
}{
|
|
||||||
{0, 0, '1'},
|
|
||||||
{1, 0, '2'},
|
|
||||||
// tabs
|
|
||||||
{0, 1, ' '},
|
|
||||||
{1, 1, ' '},
|
|
||||||
{2, 1, ' '},
|
|
||||||
{3, 1, ' '},
|
|
||||||
{4, 1, '3'},
|
|
||||||
// tab spanning 2 rows
|
|
||||||
{78, 3, ' '},
|
|
||||||
{79, 3, ' '},
|
|
||||||
{0, 4, ' '},
|
|
||||||
{1, 4, ' '},
|
|
||||||
{2, 4, '9'},
|
|
||||||
//
|
|
||||||
{0, 2, '5'},
|
|
||||||
{1, 2, '6'},
|
|
||||||
{2, 2, '8'}, // overwritten by BS
|
|
||||||
{79, 23, '!'},
|
|
||||||
}
|
|
||||||
|
|
||||||
for specIndex, spec := range specs {
|
|
||||||
ch := (byte)(fb[(spec.y*vt.width)+spec.x] & 0xFF)
|
|
||||||
if ch != spec.expChar {
|
|
||||||
t.Errorf("[spec %d] expected char at (%d, %d) to be %c; got %c", specIndex, spec.x, spec.y, spec.expChar, ch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
package console
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
// 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)
|
|
||||||
}
|
|
@ -1,100 +0,0 @@
|
|||||||
package console
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
clearColor = Black
|
|
||||||
clearChar = byte(' ')
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ega 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 Ega struct {
|
|
||||||
width uint16
|
|
||||||
height uint16
|
|
||||||
|
|
||||||
fb []uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init sets up the console.
|
|
||||||
func (cons *Ega) Init(width, height uint16, fbPhysAddr uintptr) {
|
|
||||||
cons.width = width
|
|
||||||
cons.height = height
|
|
||||||
|
|
||||||
cons.fb = *(*[]uint16)(unsafe.Pointer(&reflect.SliceHeader{
|
|
||||||
Len: int(cons.width * cons.height),
|
|
||||||
Cap: int(cons.width * cons.height),
|
|
||||||
Data: fbPhysAddr,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear clears the specified rectangular region
|
|
||||||
func (cons *Ega) 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 *Ega) Dimensions() (uint16, uint16) {
|
|
||||||
return cons.width, cons.height
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scroll a particular number of lines to the specified direction.
|
|
||||||
func (cons *Ega) 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 *Ega) 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)
|
|
||||||
}
|
|
@ -1,212 +0,0 @@
|
|||||||
package console
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEgaInit(t *testing.T) {
|
|
||||||
var cons Ega
|
|
||||||
cons.Init(80, 25, 0xB8000)
|
|
||||||
|
|
||||||
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 TestEgaClear(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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
fb := make([]uint16, 80*25)
|
|
||||||
var cons Ega
|
|
||||||
cons.Init(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
|
||||||
|
|
||||||
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++ {
|
|
||||||
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 := 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 TestEgaScrollUp(t *testing.T) {
|
|
||||||
specs := []uint16{
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
}
|
|
||||||
|
|
||||||
fb := make([]uint16, 80*25)
|
|
||||||
var cons Ega
|
|
||||||
cons.Init(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
|
||||||
|
|
||||||
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++ {
|
|
||||||
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 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 TestEgaScrollDown(t *testing.T) {
|
|
||||||
specs := []uint16{
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
}
|
|
||||||
|
|
||||||
fb := make([]uint16, 80*25)
|
|
||||||
var cons Ega
|
|
||||||
cons.Init(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
|
||||||
|
|
||||||
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++ {
|
|
||||||
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 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 TestEgaWriteWithOffScreenCoords(t *testing.T) {
|
|
||||||
fb := make([]uint16, 80*25)
|
|
||||||
var cons Ega
|
|
||||||
cons.Init(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
|
||||||
|
|
||||||
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++ {
|
|
||||||
fb[i] = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
cons.Write('!', Red, spec.x, spec.y)
|
|
||||||
|
|
||||||
for i := 0; i < len(cons.fb); i++ {
|
|
||||||
if got := fb[i]; got != 0 {
|
|
||||||
t.Errorf("[spec %d] expected Write() with off-screen coords to be a no-op", specIndex)
|
|
||||||
continue nextSpec
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEgaWrite(t *testing.T) {
|
|
||||||
fb := make([]uint16, 80*25)
|
|
||||||
var cons Ega
|
|
||||||
cons.Init(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
|
||||||
|
|
||||||
attr := (Black << 4) | Red
|
|
||||||
cons.Write('!', attr, 0, 0)
|
|
||||||
|
|
||||||
expVal := uint16(attr<<8) | uint16('!')
|
|
||||||
if got := fb[0]; got != expVal {
|
|
||||||
t.Errorf("expected call to Write() to set fb[0] to %d; got %d", expVal, got)
|
|
||||||
}
|
|
||||||
}
|
|
@ -266,6 +266,7 @@ func TestInit(t *testing.T) {
|
|||||||
modulesInitFn = modulesInit
|
modulesInitFn = modulesInit
|
||||||
typeLinksInitFn = typeLinksInit
|
typeLinksInitFn = typeLinksInit
|
||||||
itabsInitFn = itabsInit
|
itabsInitFn = itabsInit
|
||||||
|
initGoPackagesFn = initGoPackages
|
||||||
}()
|
}()
|
||||||
|
|
||||||
mallocInitFn = func() {}
|
mallocInitFn = func() {}
|
||||||
@ -273,6 +274,7 @@ func TestInit(t *testing.T) {
|
|||||||
modulesInitFn = func() {}
|
modulesInitFn = func() {}
|
||||||
typeLinksInitFn = func() {}
|
typeLinksInitFn = func() {}
|
||||||
itabsInitFn = func() {}
|
itabsInitFn = func() {}
|
||||||
|
initGoPackagesFn = func() {}
|
||||||
|
|
||||||
if err := Init(); err != nil {
|
if err := Init(); err != nil {
|
||||||
t.Fatal(t)
|
t.Fatal(t)
|
||||||
|
@ -1,23 +1,67 @@
|
|||||||
package hal
|
package hal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gopheros/kernel/driver/tty"
|
"gopheros/device"
|
||||||
"gopheros/kernel/driver/video/console"
|
"gopheros/device/tty"
|
||||||
"gopheros/kernel/hal/multiboot"
|
"gopheros/device/video/console"
|
||||||
|
"gopheros/kernel/kfmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// managedDevices contains the devices discovered by the HAL.
|
||||||
egaConsole = &console.Ega{}
|
type managedDevices struct {
|
||||||
|
activeConsole console.Device
|
||||||
// ActiveTerminal points to the currently active terminal.
|
activeTTY tty.Device
|
||||||
ActiveTerminal = &tty.Vt{}
|
}
|
||||||
)
|
|
||||||
|
var devices managedDevices
|
||||||
// InitTerminal provides a basic terminal to allow the kernel to emit some output
|
|
||||||
// till everything is properly setup
|
// ActiveTTY returns the currently active TTY
|
||||||
func InitTerminal() {
|
func ActiveTTY() tty.Device {
|
||||||
fbInfo := multiboot.GetFramebufferInfo()
|
return devices.activeTTY
|
||||||
|
}
|
||||||
egaConsole.Init(uint16(fbInfo.Width), uint16(fbInfo.Height), uintptr(fbInfo.PhysAddr))
|
|
||||||
ActiveTerminal.AttachTo(egaConsole)
|
// DetectHardware probes for hardware devices and initializes the appropriate
|
||||||
|
// drivers.
|
||||||
|
func DetectHardware() {
|
||||||
|
consoles := probe(console.HWProbes())
|
||||||
|
if len(consoles) != 0 {
|
||||||
|
devices.activeConsole = consoles[0].(console.Device)
|
||||||
|
}
|
||||||
|
|
||||||
|
ttys := probe(tty.HWProbes())
|
||||||
|
if len(ttys) != 0 {
|
||||||
|
devices.activeTTY = ttys[0].(tty.Device)
|
||||||
|
devices.activeTTY.AttachTo(devices.activeConsole)
|
||||||
|
kfmt.SetOutputSink(devices.activeTTY)
|
||||||
|
|
||||||
|
// Sync terminal contents with console
|
||||||
|
devices.activeTTY.SetState(tty.StateActive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// probe executes the supplied hw probe functions and attempts to initialize
|
||||||
|
// each detected device. The function returns a list of device drivers that
|
||||||
|
// were successfully initialized.
|
||||||
|
func probe(hwProbeFns []device.ProbeFn) []device.Driver {
|
||||||
|
var drivers []device.Driver
|
||||||
|
|
||||||
|
for _, probeFn := range hwProbeFns {
|
||||||
|
drv := probeFn()
|
||||||
|
if drv == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
major, minor, patch := drv.DriverVersion()
|
||||||
|
|
||||||
|
kfmt.Printf("[hal] %s(%d.%d.%d): ", drv.DriverName(), major, minor, patch)
|
||||||
|
if err := drv.DriverInit(); err != nil {
|
||||||
|
kfmt.Printf("init failed: %s\n", err.Message)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
drivers = append(drivers, drv)
|
||||||
|
kfmt.Printf("initialized\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return drivers
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package irq
|
package irq
|
||||||
|
|
||||||
import "gopheros/kernel/kfmt/early"
|
import "gopheros/kernel/kfmt"
|
||||||
|
|
||||||
// Regs contains a snapshot of the register values when an interrupt occurred.
|
// Regs contains a snapshot of the register values when an interrupt occurred.
|
||||||
type Regs struct {
|
type Regs struct {
|
||||||
@ -23,14 +23,14 @@ type Regs struct {
|
|||||||
|
|
||||||
// Print outputs a dump of the register values to the active console.
|
// Print outputs a dump of the register values to the active console.
|
||||||
func (r *Regs) Print() {
|
func (r *Regs) Print() {
|
||||||
early.Printf("RAX = %16x RBX = %16x\n", r.RAX, r.RBX)
|
kfmt.Printf("RAX = %16x RBX = %16x\n", r.RAX, r.RBX)
|
||||||
early.Printf("RCX = %16x RDX = %16x\n", r.RCX, r.RDX)
|
kfmt.Printf("RCX = %16x RDX = %16x\n", r.RCX, r.RDX)
|
||||||
early.Printf("RSI = %16x RDI = %16x\n", r.RSI, r.RDI)
|
kfmt.Printf("RSI = %16x RDI = %16x\n", r.RSI, r.RDI)
|
||||||
early.Printf("RBP = %16x\n", r.RBP)
|
kfmt.Printf("RBP = %16x\n", r.RBP)
|
||||||
early.Printf("R8 = %16x R9 = %16x\n", r.R8, r.R9)
|
kfmt.Printf("R8 = %16x R9 = %16x\n", r.R8, r.R9)
|
||||||
early.Printf("R10 = %16x R11 = %16x\n", r.R10, r.R11)
|
kfmt.Printf("R10 = %16x R11 = %16x\n", r.R10, r.R11)
|
||||||
early.Printf("R12 = %16x R13 = %16x\n", r.R12, r.R13)
|
kfmt.Printf("R12 = %16x R13 = %16x\n", r.R12, r.R13)
|
||||||
early.Printf("R14 = %16x R15 = %16x\n", r.R14, r.R15)
|
kfmt.Printf("R14 = %16x R15 = %16x\n", r.R14, r.R15)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Frame describes an exception frame that is automatically pushed by the CPU
|
// Frame describes an exception frame that is automatically pushed by the CPU
|
||||||
@ -45,7 +45,7 @@ type Frame struct {
|
|||||||
|
|
||||||
// Print outputs a dump of the exception frame to the active console.
|
// Print outputs a dump of the exception frame to the active console.
|
||||||
func (f *Frame) Print() {
|
func (f *Frame) Print() {
|
||||||
early.Printf("RIP = %16x CS = %16x\n", f.RIP, f.CS)
|
kfmt.Printf("RIP = %16x CS = %16x\n", f.RIP, f.CS)
|
||||||
early.Printf("RSP = %16x SS = %16x\n", f.RSP, f.SS)
|
kfmt.Printf("RSP = %16x SS = %16x\n", f.RSP, f.SS)
|
||||||
early.Printf("RFL = %16x\n", f.RFlags)
|
kfmt.Printf("RFL = %16x\n", f.RFlags)
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,16 @@ package irq
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"gopheros/kernel/driver/video/console"
|
"gopheros/kernel/kfmt"
|
||||||
"gopheros/kernel/hal"
|
|
||||||
"testing"
|
"testing"
|
||||||
"unsafe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRegsPrint(t *testing.T) {
|
func TestRegsPrint(t *testing.T) {
|
||||||
fb := mockTTY()
|
defer func() {
|
||||||
|
kfmt.SetOutputSink(nil)
|
||||||
|
}()
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
regs := Regs{
|
regs := Regs{
|
||||||
RAX: 1,
|
RAX: 1,
|
||||||
RBX: 2,
|
RBX: 2,
|
||||||
@ -29,15 +31,20 @@ func TestRegsPrint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
regs.Print()
|
regs.Print()
|
||||||
|
|
||||||
exp := "RAX = 0000000000000001 RBX = 0000000000000002\nRCX = 0000000000000003 RDX = 0000000000000004\nRSI = 0000000000000005 RDI = 0000000000000006\nRBP = 0000000000000007\nR8 = 0000000000000008 R9 = 0000000000000009\nR10 = 000000000000000a R11 = 000000000000000b\nR12 = 000000000000000c R13 = 000000000000000d\nR14 = 000000000000000e R15 = 000000000000000f"
|
exp := "RAX = 0000000000000001 RBX = 0000000000000002\nRCX = 0000000000000003 RDX = 0000000000000004\nRSI = 0000000000000005 RDI = 0000000000000006\nRBP = 0000000000000007\nR8 = 0000000000000008 R9 = 0000000000000009\nR10 = 000000000000000a R11 = 000000000000000b\nR12 = 000000000000000c R13 = 000000000000000d\nR14 = 000000000000000e R15 = 000000000000000f\n"
|
||||||
|
|
||||||
if got := readTTY(fb); got != exp {
|
kfmt.SetOutputSink(&buf)
|
||||||
|
if got := buf.String(); got != exp {
|
||||||
t.Fatalf("expected to get:\n%q\ngot:\n%q", exp, got)
|
t.Fatalf("expected to get:\n%q\ngot:\n%q", exp, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFramePrint(t *testing.T) {
|
func TestFramePrint(t *testing.T) {
|
||||||
fb := mockTTY()
|
defer func() {
|
||||||
|
kfmt.SetOutputSink(nil)
|
||||||
|
}()
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
frame := Frame{
|
frame := Frame{
|
||||||
RIP: 1,
|
RIP: 1,
|
||||||
CS: 2,
|
CS: 2,
|
||||||
@ -47,37 +54,11 @@ func TestFramePrint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
frame.Print()
|
frame.Print()
|
||||||
|
|
||||||
exp := "RIP = 0000000000000001 CS = 0000000000000002\nRSP = 0000000000000004 SS = 0000000000000005\nRFL = 0000000000000003"
|
exp := "RIP = 0000000000000001 CS = 0000000000000002\nRSP = 0000000000000004 SS = 0000000000000005\nRFL = 0000000000000003\n"
|
||||||
|
|
||||||
if got := readTTY(fb); got != exp {
|
kfmt.SetOutputSink(&buf)
|
||||||
|
if got := buf.String(); got != exp {
|
||||||
t.Fatalf("expected to get:\n%q\ngot:\n%q", exp, got)
|
t.Fatalf("expected to get:\n%q\ngot:\n%q", exp, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func readTTY(fb []byte) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
for i := 0; i < len(fb); i += 2 {
|
|
||||||
ch := fb[i]
|
|
||||||
if ch == 0 {
|
|
||||||
if i+2 < len(fb) && fb[i+2] != 0 {
|
|
||||||
buf.WriteByte('\n')
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteByte(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func mockTTY() []byte {
|
|
||||||
// Mock a tty to handle early.Printf output
|
|
||||||
mockConsoleFb := make([]byte, 160*25)
|
|
||||||
mockConsole := &console.Ega{}
|
|
||||||
mockConsole.Init(80, 25, uintptr(unsafe.Pointer(&mockConsoleFb[0])))
|
|
||||||
hal.ActiveTerminal.AttachTo(mockConsole)
|
|
||||||
|
|
||||||
return mockConsoleFb
|
|
||||||
}
|
|
||||||
|
@ -1,20 +1,48 @@
|
|||||||
package early
|
package kfmt
|
||||||
|
|
||||||
import "gopheros/kernel/hal"
|
import (
|
||||||
|
"io"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// maxBufSize defines the buffer size for formatting numbers.
|
||||||
|
const maxBufSize = 32
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errMissingArg = []byte("(MISSING)")
|
errMissingArg = []byte("(MISSING)")
|
||||||
errWrongArgType = []byte("%!(WRONGTYPE)")
|
errWrongArgType = []byte("%!(WRONGTYPE)")
|
||||||
errNoVerb = []byte("%!(NOVERB)")
|
errNoVerb = []byte("%!(NOVERB)")
|
||||||
errExtraArg = []byte("%!(EXTRA)")
|
errExtraArg = []byte("%!(EXTRA)")
|
||||||
padding = byte(' ')
|
|
||||||
trueValue = []byte("true")
|
trueValue = []byte("true")
|
||||||
falseValue = []byte("false")
|
falseValue = []byte("false")
|
||||||
|
|
||||||
|
numFmtBuf = []byte("012345678901234567890123456789012")
|
||||||
|
|
||||||
|
// singleByte is used as a shared buffer for passing single characters
|
||||||
|
// to doWrite.
|
||||||
|
singleByte = []byte(" ")
|
||||||
|
|
||||||
|
// earlyPrintBuffer is a ring buffer that stores Printf output before the
|
||||||
|
// console and TTYs are initialized.
|
||||||
|
earlyPrintBuffer ringBuffer
|
||||||
|
|
||||||
|
// outputSink is a io.Writer where Printf will send its output. If set
|
||||||
|
// to nil, then the output will be redirected to the earlyPrintBuffer.
|
||||||
|
outputSink io.Writer
|
||||||
)
|
)
|
||||||
|
|
||||||
// Printf provides a minimal Printf implementation that can be used before the
|
// SetOutputSink sets the default target for calls to Printf to w and copies
|
||||||
// Go runtime has been properly initialized. This version of printf does not
|
// any data accumulated in the earlyPrintBuffer to itt .
|
||||||
// allocate any memory and uses hal.ActiveTerminal for its output.
|
func SetOutputSink(w io.Writer) {
|
||||||
|
outputSink = w
|
||||||
|
if w != nil {
|
||||||
|
io.Copy(w, &earlyPrintBuffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf provides a minimal Printf implementation that can be safely used
|
||||||
|
// before the Go runtime has been properly initialized. This implementation
|
||||||
|
// does not allocate any memory.
|
||||||
//
|
//
|
||||||
// Similar to fmt.Printf, this version of printf supports the following subset
|
// Similar to fmt.Printf, this version of printf supports the following subset
|
||||||
// of formatting verbs:
|
// of formatting verbs:
|
||||||
@ -46,7 +74,17 @@ var (
|
|||||||
// starts generating calls to runtime.convT2E (which calls runtime.newobject)
|
// starts generating calls to runtime.convT2E (which calls runtime.newobject)
|
||||||
// when assembling the argument slice which obviously will crash the kernel since
|
// when assembling the argument slice which obviously will crash the kernel since
|
||||||
// memory management is not yet available.
|
// memory management is not yet available.
|
||||||
|
//
|
||||||
|
// The output of Printf is written to the currently active TTY. If no TTY is
|
||||||
|
// available, then the output is buffered into a ring-buffer and can be
|
||||||
|
// retrieved by a call to FlushRingBuffer.
|
||||||
func Printf(format string, args ...interface{}) {
|
func Printf(format string, args ...interface{}) {
|
||||||
|
Fprintf(outputSink, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintf behaves exactly like Printf but it writes the formatted output to
|
||||||
|
// the specified io.Writer.
|
||||||
|
func Fprintf(w io.Writer, format string, args ...interface{}) {
|
||||||
var (
|
var (
|
||||||
nextCh byte
|
nextCh byte
|
||||||
nextArgIndex int
|
nextArgIndex int
|
||||||
@ -62,8 +100,11 @@ func Printf(format string, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if blockStart < blockEnd {
|
if blockStart < blockEnd {
|
||||||
|
// passing format[blockStart:blockEnd] to doWrite triggers a
|
||||||
|
// memory allocation so we need to do this one byte at a time.
|
||||||
for i := blockStart; i < blockEnd; i++ {
|
for i := blockStart; i < blockEnd; i++ {
|
||||||
hal.ActiveTerminal.WriteByte(format[i])
|
singleByte[0] = format[i]
|
||||||
|
doWrite(w, singleByte)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +116,8 @@ func Printf(format string, args ...interface{}) {
|
|||||||
nextCh = format[blockEnd]
|
nextCh = format[blockEnd]
|
||||||
switch {
|
switch {
|
||||||
case nextCh == '%':
|
case nextCh == '%':
|
||||||
hal.ActiveTerminal.Write([]byte{'%'})
|
singleByte[0] = '%'
|
||||||
|
doWrite(w, singleByte)
|
||||||
break parseFmt
|
break parseFmt
|
||||||
case nextCh >= '0' && nextCh <= '9':
|
case nextCh >= '0' && nextCh <= '9':
|
||||||
padLen = (padLen * 10) + int(nextCh-'0')
|
padLen = (padLen * 10) + int(nextCh-'0')
|
||||||
@ -83,21 +125,21 @@ func Printf(format string, args ...interface{}) {
|
|||||||
case nextCh == 'd' || nextCh == 'x' || nextCh == 'o' || nextCh == 's' || nextCh == 't':
|
case nextCh == 'd' || nextCh == 'x' || nextCh == 'o' || nextCh == 's' || nextCh == 't':
|
||||||
// Run out of args to print
|
// Run out of args to print
|
||||||
if nextArgIndex >= len(args) {
|
if nextArgIndex >= len(args) {
|
||||||
hal.ActiveTerminal.Write(errMissingArg)
|
doWrite(w, errMissingArg)
|
||||||
break parseFmt
|
break parseFmt
|
||||||
}
|
}
|
||||||
|
|
||||||
switch nextCh {
|
switch nextCh {
|
||||||
case 'o':
|
case 'o':
|
||||||
fmtInt(args[nextArgIndex], 8, padLen)
|
fmtInt(w, args[nextArgIndex], 8, padLen)
|
||||||
case 'd':
|
case 'd':
|
||||||
fmtInt(args[nextArgIndex], 10, padLen)
|
fmtInt(w, args[nextArgIndex], 10, padLen)
|
||||||
case 'x':
|
case 'x':
|
||||||
fmtInt(args[nextArgIndex], 16, padLen)
|
fmtInt(w, args[nextArgIndex], 16, padLen)
|
||||||
case 's':
|
case 's':
|
||||||
fmtString(args[nextArgIndex], padLen)
|
fmtString(w, args[nextArgIndex], padLen)
|
||||||
case 't':
|
case 't':
|
||||||
fmtBool(args[nextArgIndex])
|
fmtBool(w, args[nextArgIndex])
|
||||||
}
|
}
|
||||||
|
|
||||||
nextArgIndex++
|
nextArgIndex++
|
||||||
@ -105,80 +147,87 @@ func Printf(format string, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// reached end of formatting string without finding a verb
|
// reached end of formatting string without finding a verb
|
||||||
hal.ActiveTerminal.Write(errNoVerb)
|
doWrite(w, errNoVerb)
|
||||||
}
|
}
|
||||||
blockStart, blockEnd = blockEnd+1, blockEnd+1
|
blockStart, blockEnd = blockEnd+1, blockEnd+1
|
||||||
}
|
}
|
||||||
|
|
||||||
if blockStart != blockEnd {
|
if blockStart != blockEnd {
|
||||||
|
// passing format[blockStart:blockEnd] to doWrite triggers a
|
||||||
|
// memory allocation so we need to do this one byte at a time.
|
||||||
for i := blockStart; i < blockEnd; i++ {
|
for i := blockStart; i < blockEnd; i++ {
|
||||||
hal.ActiveTerminal.WriteByte(format[i])
|
singleByte[0] = format[i]
|
||||||
|
doWrite(w, singleByte)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for unused args
|
// Check for unused args
|
||||||
for ; nextArgIndex < len(args); nextArgIndex++ {
|
for ; nextArgIndex < len(args); nextArgIndex++ {
|
||||||
hal.ActiveTerminal.Write(errExtraArg)
|
doWrite(w, errExtraArg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fmtBool prints a formatted version of boolean value v using hal.ActiveTerminal
|
// fmtBool prints a formatted version of boolean value v.
|
||||||
// for its output.
|
func fmtBool(w io.Writer, v interface{}) {
|
||||||
func fmtBool(v interface{}) {
|
|
||||||
switch bVal := v.(type) {
|
switch bVal := v.(type) {
|
||||||
case bool:
|
case bool:
|
||||||
switch bVal {
|
switch bVal {
|
||||||
case true:
|
case true:
|
||||||
hal.ActiveTerminal.Write(trueValue)
|
doWrite(w, trueValue)
|
||||||
case false:
|
case false:
|
||||||
hal.ActiveTerminal.Write(falseValue)
|
doWrite(w, falseValue)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
hal.ActiveTerminal.Write(errWrongArgType)
|
doWrite(w, errWrongArgType)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fmtString prints a formatted version of string or []byte value v, applying the
|
// fmtString prints a formatted version of string or []byte value v, applying
|
||||||
// padding specified by padLen. This function uses hal.ActiveTerminal for its
|
// the padding specified by padLen.
|
||||||
// output.
|
func fmtString(w io.Writer, v interface{}, padLen int) {
|
||||||
func fmtString(v interface{}, padLen int) {
|
|
||||||
switch castedVal := v.(type) {
|
switch castedVal := v.(type) {
|
||||||
case string:
|
case string:
|
||||||
fmtRepeat(padding, padLen-len(castedVal))
|
fmtRepeat(w, ' ', padLen-len(castedVal))
|
||||||
|
// converting the string to a byte slice triggers a memory allocation
|
||||||
|
// so we need to do this one byte at a time.
|
||||||
for i := 0; i < len(castedVal); i++ {
|
for i := 0; i < len(castedVal); i++ {
|
||||||
hal.ActiveTerminal.WriteByte(castedVal[i])
|
singleByte[0] = castedVal[i]
|
||||||
|
doWrite(w, singleByte)
|
||||||
}
|
}
|
||||||
case []byte:
|
case []byte:
|
||||||
fmtRepeat(padding, padLen-len(castedVal))
|
fmtRepeat(w, ' ', padLen-len(castedVal))
|
||||||
hal.ActiveTerminal.Write(castedVal)
|
doWrite(w, castedVal)
|
||||||
default:
|
default:
|
||||||
hal.ActiveTerminal.Write(errWrongArgType)
|
doWrite(w, errWrongArgType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fmtRepeat writes count bytes with value ch to the hal.ActiveTerminal.
|
// fmtRepeat writes count bytes with value ch.
|
||||||
func fmtRepeat(ch byte, count int) {
|
func fmtRepeat(w io.Writer, ch byte, count int) {
|
||||||
|
singleByte[0] = ch
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
hal.ActiveTerminal.WriteByte(ch)
|
doWrite(w, singleByte)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fmtInt prints out a formatted version of v in the requested base, applying the
|
// fmtInt prints out a formatted version of v in the requested base, applying
|
||||||
// padding specified by padLen. This function uses hal.ActiveTerminal for its
|
// the padding specified by padLen. This function supports all built-in signed
|
||||||
// output, supports all built-in signed and unsigned integer types and supports
|
// and unsigned integer types and base 8, 10 and 16 output.
|
||||||
// base 8, 10 and 16 output.
|
func fmtInt(w io.Writer, v interface{}, base, padLen int) {
|
||||||
func fmtInt(v interface{}, base, padLen int) {
|
|
||||||
var (
|
var (
|
||||||
sval int64
|
sval int64
|
||||||
uval uint64
|
uval uint64
|
||||||
divider uint64
|
divider uint64
|
||||||
remainder uint64
|
remainder uint64
|
||||||
buf [20]byte
|
|
||||||
padCh byte
|
padCh byte
|
||||||
left, right, end int
|
left, right, end int
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if padLen >= maxBufSize {
|
||||||
|
padLen = maxBufSize - 1
|
||||||
|
}
|
||||||
|
|
||||||
switch base {
|
switch base {
|
||||||
case 8:
|
case 8:
|
||||||
divider = 8
|
divider = 8
|
||||||
@ -213,7 +262,7 @@ func fmtInt(v interface{}, base, padLen int) {
|
|||||||
case int:
|
case int:
|
||||||
sval = int64(v.(int))
|
sval = int64(v.(int))
|
||||||
default:
|
default:
|
||||||
hal.ActiveTerminal.Write(errWrongArgType)
|
doWrite(w, errWrongArgType)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,13 +273,13 @@ func fmtInt(v interface{}, base, padLen int) {
|
|||||||
uval = uint64(sval)
|
uval = uint64(sval)
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for right < maxBufSize {
|
||||||
remainder = uval % divider
|
remainder = uval % divider
|
||||||
if remainder < 10 {
|
if remainder < 10 {
|
||||||
buf[right] = byte(remainder) + '0'
|
numFmtBuf[right] = byte(remainder) + '0'
|
||||||
} else {
|
} else {
|
||||||
// map values from 10 to 15 -> a-f
|
// map values from 10 to 15 -> a-f
|
||||||
buf[right] = byte(remainder-10) + 'a'
|
numFmtBuf[right] = byte(remainder-10) + 'a'
|
||||||
}
|
}
|
||||||
|
|
||||||
right++
|
right++
|
||||||
@ -243,27 +292,55 @@ func fmtInt(v interface{}, base, padLen int) {
|
|||||||
|
|
||||||
// Apply padding if required
|
// Apply padding if required
|
||||||
for ; right-left < padLen; right++ {
|
for ; right-left < padLen; right++ {
|
||||||
buf[right] = padCh
|
numFmtBuf[right] = padCh
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply negative sign to the rightmost blank character (if using enough padding);
|
// Apply negative sign to the rightmost blank character (if using enough padding);
|
||||||
// otherwise append the sign as a new char
|
// otherwise append the sign as a new char
|
||||||
if sval < 0 {
|
if sval < 0 {
|
||||||
for end = right - 1; buf[end] == ' '; end-- {
|
for end = right - 1; numFmtBuf[end] == ' '; end-- {
|
||||||
}
|
}
|
||||||
|
|
||||||
if end == right-1 {
|
if end == right-1 {
|
||||||
right++
|
right++
|
||||||
}
|
}
|
||||||
|
|
||||||
buf[end+1] = '-'
|
numFmtBuf[end+1] = '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reverse in place
|
// Reverse in place
|
||||||
end = right
|
end = right
|
||||||
for right = right - 1; left < right; left, right = left+1, right-1 {
|
for right = right - 1; left < right; left, right = left+1, right-1 {
|
||||||
buf[left], buf[right] = buf[right], buf[left]
|
numFmtBuf[left], numFmtBuf[right] = numFmtBuf[right], numFmtBuf[left]
|
||||||
}
|
}
|
||||||
|
|
||||||
hal.ActiveTerminal.Write(buf[0:end])
|
doWrite(w, numFmtBuf[0:end])
|
||||||
|
}
|
||||||
|
|
||||||
|
// doWrite is a proxy that uses the runtime.noescape hack to hide p from the
|
||||||
|
// compiler's escape analysis. Without this hack, the compiler cannot properly
|
||||||
|
// detect that p does not escape (due to the call to the yet unknown outputSink
|
||||||
|
// io.Writer) and plays it safe by flagging it as escaping. This causes all
|
||||||
|
// calls to Printf to call runtime.convT2E which triggers a memory allocation
|
||||||
|
// causing the kernel to crash if a call to Printf is made before the Go
|
||||||
|
// allocator is initialized.
|
||||||
|
func doWrite(w io.Writer, p []byte) {
|
||||||
|
doRealWrite(w, noEscape(unsafe.Pointer(&p)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func doRealWrite(w io.Writer, bufPtr unsafe.Pointer) {
|
||||||
|
p := *(*[]byte)(bufPtr)
|
||||||
|
if w != nil {
|
||||||
|
w.Write(p)
|
||||||
|
} else {
|
||||||
|
earlyPrintBuffer.Write(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// noEscape hides a pointer from escape analysis. This function is copied over
|
||||||
|
// from runtime/stubs.go
|
||||||
|
//go:nosplit
|
||||||
|
func noEscape(p unsafe.Pointer) unsafe.Pointer {
|
||||||
|
x := uintptr(p)
|
||||||
|
return unsafe.Pointer(x ^ 0)
|
||||||
}
|
}
|
@ -1,31 +1,20 @@
|
|||||||
package early
|
package kfmt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"gopheros/kernel/driver/tty"
|
"fmt"
|
||||||
"gopheros/kernel/driver/video/console"
|
"strings"
|
||||||
"gopheros/kernel/hal"
|
|
||||||
"testing"
|
"testing"
|
||||||
"unsafe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPrintf(t *testing.T) {
|
func TestPrintf(t *testing.T) {
|
||||||
origTerm := hal.ActiveTerminal
|
|
||||||
defer func() {
|
defer func() {
|
||||||
hal.ActiveTerminal = origTerm
|
outputSink = nil
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// mute vet warnings about malformed printf formatting strings
|
// mute vet warnings about malformed printf formatting strings
|
||||||
printfn := Printf
|
printfn := Printf
|
||||||
|
|
||||||
ega := &console.Ega{}
|
|
||||||
fb := make([]uint8, 160*25)
|
|
||||||
ega.Init(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
|
||||||
|
|
||||||
vt := &tty.Vt{}
|
|
||||||
vt.AttachTo(ega)
|
|
||||||
hal.ActiveTerminal = vt
|
|
||||||
|
|
||||||
specs := []struct {
|
specs := []struct {
|
||||||
fn func()
|
fn func()
|
||||||
expOutput string
|
expOutput string
|
||||||
@ -124,6 +113,10 @@ func TestPrintf(t *testing.T) {
|
|||||||
func() { printfn("int arg longer than padding: '%5x'", int(-0xbadf00d)) },
|
func() { printfn("int arg longer than padding: '%5x'", int(-0xbadf00d)) },
|
||||||
"int arg longer than padding: '-badf00d'",
|
"int arg longer than padding: '-badf00d'",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
func() { printfn("padding longer than maxBufSize '%128x'", int(-0xbadf00d)) },
|
||||||
|
fmt.Sprintf("padding longer than maxBufSize '-%sbadf00d'", strings.Repeat("0", maxBufSize-8)),
|
||||||
|
},
|
||||||
// multiple arguments
|
// multiple arguments
|
||||||
{
|
{
|
||||||
func() { printfn("%%%s%d%t", "foo", 123, true) },
|
func() { printfn("%%%s%d%t", "foo", 123, true) },
|
||||||
@ -156,25 +149,42 @@ func TestPrintf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for specIndex, spec := range specs {
|
var buf bytes.Buffer
|
||||||
for index := 0; index < len(fb); index++ {
|
SetOutputSink(&buf)
|
||||||
fb[index] = 0
|
|
||||||
}
|
|
||||||
vt.SetPosition(0, 0)
|
|
||||||
|
|
||||||
|
for specIndex, spec := range specs {
|
||||||
|
buf.Reset()
|
||||||
spec.fn()
|
spec.fn()
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
for index := 0; ; index += 2 {
|
|
||||||
if fb[index] == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteByte(fb[index])
|
|
||||||
}
|
|
||||||
|
|
||||||
if got := buf.String(); got != spec.expOutput {
|
if got := buf.String(); got != spec.expOutput {
|
||||||
t.Errorf("[spec %d] expected to get %q; got %q", specIndex, spec.expOutput, got)
|
t.Errorf("[spec %d] expected to get\n%q\ngot:\n%q", specIndex, spec.expOutput, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPrintfToRingBuffer(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
outputSink = nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
exp := "hello world"
|
||||||
|
Printf(exp)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
SetOutputSink(&buf)
|
||||||
|
|
||||||
|
if got := buf.String(); got != exp {
|
||||||
|
t.Fatalf("expected to get:\n%q\ngot:\n%q", exp, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFprintf(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
exp := "hello world"
|
||||||
|
Fprintf(&buf, exp)
|
||||||
|
|
||||||
|
if got := buf.String(); got != exp {
|
||||||
|
t.Fatalf("expected to get:\n%q\ngot:\n%q", exp, got)
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,15 @@
|
|||||||
package kernel
|
package kfmt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"gopheros/kernel"
|
||||||
"gopheros/kernel/cpu"
|
"gopheros/kernel/cpu"
|
||||||
"gopheros/kernel/kfmt/early"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// cpuHaltFn is mocked by tests and is automatically inlined by the compiler.
|
// cpuHaltFn is mocked by tests and is automatically inlined by the compiler.
|
||||||
cpuHaltFn = cpu.Halt
|
cpuHaltFn = cpu.Halt
|
||||||
|
|
||||||
errRuntimePanic = &Error{Module: "rt", Message: "unknown cause"}
|
errRuntimePanic = &kernel.Error{Module: "rt", Message: "unknown cause"}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Panic outputs the supplied error (if not nil) to the console and halts the
|
// Panic outputs the supplied error (if not nil) to the console and halts the
|
||||||
@ -17,10 +17,10 @@ var (
|
|||||||
// for calls to panic() (resolved via runtime.gopanic)
|
// for calls to panic() (resolved via runtime.gopanic)
|
||||||
//go:redirect-from runtime.gopanic
|
//go:redirect-from runtime.gopanic
|
||||||
func Panic(e interface{}) {
|
func Panic(e interface{}) {
|
||||||
var err *Error
|
var err *kernel.Error
|
||||||
|
|
||||||
switch t := e.(type) {
|
switch t := e.(type) {
|
||||||
case *Error:
|
case *kernel.Error:
|
||||||
err = t
|
err = t
|
||||||
case string:
|
case string:
|
||||||
panicString(t)
|
panicString(t)
|
||||||
@ -30,12 +30,12 @@ func Panic(e interface{}) {
|
|||||||
err = errRuntimePanic
|
err = errRuntimePanic
|
||||||
}
|
}
|
||||||
|
|
||||||
early.Printf("\n-----------------------------------\n")
|
Printf("\n-----------------------------------\n")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
early.Printf("[%s] unrecoverable error: %s\n", err.Module, err.Message)
|
Printf("[%s] unrecoverable error: %s\n", err.Module, err.Message)
|
||||||
}
|
}
|
||||||
early.Printf("*** kernel panic: system halted ***")
|
Printf("*** kernel panic: system halted ***")
|
||||||
early.Printf("\n-----------------------------------\n")
|
Printf("\n-----------------------------------\n")
|
||||||
|
|
||||||
cpuHaltFn()
|
cpuHaltFn()
|
||||||
}
|
}
|
@ -1,20 +1,22 @@
|
|||||||
package kernel
|
package kfmt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"gopheros/kernel"
|
||||||
"gopheros/kernel/cpu"
|
"gopheros/kernel/cpu"
|
||||||
"gopheros/kernel/driver/video/console"
|
|
||||||
"gopheros/kernel/hal"
|
|
||||||
"testing"
|
"testing"
|
||||||
"unsafe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPanic(t *testing.T) {
|
func TestPanic(t *testing.T) {
|
||||||
defer func() {
|
defer func() {
|
||||||
cpuHaltFn = cpu.Halt
|
cpuHaltFn = cpu.Halt
|
||||||
|
SetOutputSink(nil)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
SetOutputSink(&buf)
|
||||||
|
|
||||||
var cpuHaltCalled bool
|
var cpuHaltCalled bool
|
||||||
cpuHaltFn = func() {
|
cpuHaltFn = func() {
|
||||||
cpuHaltCalled = true
|
cpuHaltCalled = true
|
||||||
@ -22,14 +24,14 @@ func TestPanic(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("with *kernel.Error", func(t *testing.T) {
|
t.Run("with *kernel.Error", func(t *testing.T) {
|
||||||
cpuHaltCalled = false
|
cpuHaltCalled = false
|
||||||
fb := mockTTY()
|
buf.Reset()
|
||||||
err := &Error{Module: "test", Message: "panic test"}
|
err := &kernel.Error{Module: "test", Message: "panic test"}
|
||||||
|
|
||||||
Panic(err)
|
Panic(err)
|
||||||
|
|
||||||
exp := "\n-----------------------------------\n[test] unrecoverable error: panic test\n*** kernel panic: system halted ***\n-----------------------------------"
|
exp := "\n-----------------------------------\n[test] unrecoverable error: panic test\n*** kernel panic: system halted ***\n-----------------------------------\n"
|
||||||
|
|
||||||
if got := readTTY(fb); got != exp {
|
if got := buf.String(); got != exp {
|
||||||
t.Fatalf("expected to get:\n%q\ngot:\n%q", exp, got)
|
t.Fatalf("expected to get:\n%q\ngot:\n%q", exp, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,14 +42,14 @@ func TestPanic(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("with error", func(t *testing.T) {
|
t.Run("with error", func(t *testing.T) {
|
||||||
cpuHaltCalled = false
|
cpuHaltCalled = false
|
||||||
fb := mockTTY()
|
buf.Reset()
|
||||||
err := errors.New("go error")
|
err := errors.New("go error")
|
||||||
|
|
||||||
Panic(err)
|
Panic(err)
|
||||||
|
|
||||||
exp := "\n-----------------------------------\n[rt] unrecoverable error: go error\n*** kernel panic: system halted ***\n-----------------------------------"
|
exp := "\n-----------------------------------\n[rt] unrecoverable error: go error\n*** kernel panic: system halted ***\n-----------------------------------\n"
|
||||||
|
|
||||||
if got := readTTY(fb); got != exp {
|
if got := buf.String(); got != exp {
|
||||||
t.Fatalf("expected to get:\n%q\ngot:\n%q", exp, got)
|
t.Fatalf("expected to get:\n%q\ngot:\n%q", exp, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,14 +60,14 @@ func TestPanic(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("with string", func(t *testing.T) {
|
t.Run("with string", func(t *testing.T) {
|
||||||
cpuHaltCalled = false
|
cpuHaltCalled = false
|
||||||
fb := mockTTY()
|
buf.Reset()
|
||||||
err := "string error"
|
err := "string error"
|
||||||
|
|
||||||
Panic(err)
|
Panic(err)
|
||||||
|
|
||||||
exp := "\n-----------------------------------\n[rt] unrecoverable error: string error\n*** kernel panic: system halted ***\n-----------------------------------"
|
exp := "\n-----------------------------------\n[rt] unrecoverable error: string error\n*** kernel panic: system halted ***\n-----------------------------------\n"
|
||||||
|
|
||||||
if got := readTTY(fb); got != exp {
|
if got := buf.String(); got != exp {
|
||||||
t.Fatalf("expected to get:\n%q\ngot:\n%q", exp, got)
|
t.Fatalf("expected to get:\n%q\ngot:\n%q", exp, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,13 +78,13 @@ func TestPanic(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("without error", func(t *testing.T) {
|
t.Run("without error", func(t *testing.T) {
|
||||||
cpuHaltCalled = false
|
cpuHaltCalled = false
|
||||||
fb := mockTTY()
|
buf.Reset()
|
||||||
|
|
||||||
Panic(nil)
|
Panic(nil)
|
||||||
|
|
||||||
exp := "\n-----------------------------------\n*** kernel panic: system halted ***\n-----------------------------------"
|
exp := "\n-----------------------------------\n*** kernel panic: system halted ***\n-----------------------------------\n"
|
||||||
|
|
||||||
if got := readTTY(fb); got != exp {
|
if got := buf.String(); got != exp {
|
||||||
t.Fatalf("expected to get:\n%q\ngot:\n%q", exp, got)
|
t.Fatalf("expected to get:\n%q\ngot:\n%q", exp, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,30 +93,3 @@ func TestPanic(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func readTTY(fb []byte) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
for i := 0; i < len(fb); i += 2 {
|
|
||||||
ch := fb[i]
|
|
||||||
if ch == 0 {
|
|
||||||
if i+2 < len(fb) && fb[i+2] != 0 {
|
|
||||||
buf.WriteByte('\n')
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteByte(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func mockTTY() []byte {
|
|
||||||
// Mock a tty to handle early.Printf output
|
|
||||||
mockConsoleFb := make([]byte, 160*25)
|
|
||||||
mockConsole := &console.Ega{}
|
|
||||||
mockConsole.Init(80, 25, uintptr(unsafe.Pointer(&mockConsoleFb[0])))
|
|
||||||
hal.ActiveTerminal.AttachTo(mockConsole)
|
|
||||||
|
|
||||||
return mockConsoleFb
|
|
||||||
}
|
|
65
src/gopheros/kernel/kfmt/ringbuf.go
Normal file
65
src/gopheros/kernel/kfmt/ringbuf.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package kfmt
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
// ringBufferSize defines size of the ring buffer that buffers early Printf
|
||||||
|
// output. Its default size is selected so it can buffer the contents of a
|
||||||
|
// standard 80*25 text-mode console. The ring buffer size must always be a
|
||||||
|
// power of 2.
|
||||||
|
const ringBufferSize = 2048
|
||||||
|
|
||||||
|
// ringBuffer models a ring buffer of size ringBufferSize. This buffer is used
|
||||||
|
// for capturing the output of Printf before the tty and console systems are
|
||||||
|
// initialized.
|
||||||
|
type ringBuffer struct {
|
||||||
|
buffer [ringBufferSize]byte
|
||||||
|
rIndex, wIndex int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes len(p) bytes from p to the ringBuffer.
|
||||||
|
func (rb *ringBuffer) Write(p []byte) (int, error) {
|
||||||
|
for _, b := range p {
|
||||||
|
rb.buffer[rb.wIndex] = b
|
||||||
|
rb.wIndex = (rb.wIndex + 1) & (ringBufferSize - 1)
|
||||||
|
if rb.rIndex == rb.wIndex {
|
||||||
|
rb.rIndex = (rb.rIndex + 1) & (ringBufferSize - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads up to len(p) bytes into p. It returns the number of bytes read (0
|
||||||
|
// <= n <= len(p)) and any error encountered.
|
||||||
|
func (rb *ringBuffer) Read(p []byte) (n int, err error) {
|
||||||
|
switch {
|
||||||
|
case rb.rIndex < rb.wIndex:
|
||||||
|
// read up to min(wIndex - rIndex, len(p)) bytes
|
||||||
|
n = rb.wIndex - rb.rIndex
|
||||||
|
if pLen := len(p); pLen < n {
|
||||||
|
n = pLen
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(p, rb.buffer[rb.rIndex:rb.rIndex+n])
|
||||||
|
rb.rIndex += n
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
case rb.rIndex > rb.wIndex:
|
||||||
|
// Read up to min(len(buf) - rIndex, len(p)) bytes
|
||||||
|
n = len(rb.buffer) - rb.rIndex
|
||||||
|
if pLen := len(p); pLen < n {
|
||||||
|
n = pLen
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(p, rb.buffer[rb.rIndex:rb.rIndex+n])
|
||||||
|
rb.rIndex += n
|
||||||
|
|
||||||
|
if rb.rIndex == len(rb.buffer) {
|
||||||
|
rb.rIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
default: // rIndex == wIndex
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
}
|
96
src/gopheros/kernel/kfmt/ringbuf_test.go
Normal file
96
src/gopheros/kernel/kfmt/ringbuf_test.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package kfmt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRingBuffer(t *testing.T) {
|
||||||
|
var (
|
||||||
|
buf bytes.Buffer
|
||||||
|
expStr = "the big brown fox jumped over the lazy dog"
|
||||||
|
rb ringBuffer
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run("read/write", func(t *testing.T) {
|
||||||
|
rb.wIndex = 0
|
||||||
|
rb.rIndex = 0
|
||||||
|
n, err := rb.Write([]byte(expStr))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n != len(expStr) {
|
||||||
|
t.Fatalf("expected to write %d bytes; wrote %d", len(expStr), n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := readByteByByte(&buf, &rb); got != expStr {
|
||||||
|
t.Fatalf("expected to read %q; got %q", expStr, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("write moves read pointer", func(t *testing.T) {
|
||||||
|
rb.wIndex = ringBufferSize - 1
|
||||||
|
rb.rIndex = 0
|
||||||
|
_, err := rb.Write([]byte{'!'})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exp := 1; rb.rIndex != exp {
|
||||||
|
t.Fatalf("expected write to push rIndex to %d; got %d", exp, rb.rIndex)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("wIndex < rIndex", func(t *testing.T) {
|
||||||
|
rb.wIndex = ringBufferSize - 2
|
||||||
|
rb.rIndex = ringBufferSize - 2
|
||||||
|
n, err := rb.Write([]byte(expStr))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n != len(expStr) {
|
||||||
|
t.Fatalf("expected to write %d bytes; wrote %d", len(expStr), n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := readByteByByte(&buf, &rb); got != expStr {
|
||||||
|
t.Fatalf("expected to read %q; got %q", expStr, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with io.WriteTo", func(t *testing.T) {
|
||||||
|
rb.wIndex = ringBufferSize - 2
|
||||||
|
rb.rIndex = ringBufferSize - 2
|
||||||
|
n, err := rb.Write([]byte(expStr))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n != len(expStr) {
|
||||||
|
t.Fatalf("expected to write %d bytes; wrote %d", len(expStr), n)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
io.Copy(&buf, &rb)
|
||||||
|
|
||||||
|
if got := buf.String(); got != expStr {
|
||||||
|
t.Fatalf("expected to read %q; got %q", expStr, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func readByteByByte(buf *bytes.Buffer, r io.Reader) string {
|
||||||
|
buf.Reset()
|
||||||
|
var b = make([]byte, 1)
|
||||||
|
for {
|
||||||
|
_, err := r.Read(b)
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Write(b)
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
@ -5,6 +5,7 @@ import (
|
|||||||
"gopheros/kernel/goruntime"
|
"gopheros/kernel/goruntime"
|
||||||
"gopheros/kernel/hal"
|
"gopheros/kernel/hal"
|
||||||
"gopheros/kernel/hal/multiboot"
|
"gopheros/kernel/hal/multiboot"
|
||||||
|
"gopheros/kernel/kfmt"
|
||||||
"gopheros/kernel/mem/pmm/allocator"
|
"gopheros/kernel/mem/pmm/allocator"
|
||||||
"gopheros/kernel/mem/vmm"
|
"gopheros/kernel/mem/vmm"
|
||||||
)
|
)
|
||||||
@ -27,9 +28,6 @@ var (
|
|||||||
func Kmain(multibootInfoPtr, kernelStart, kernelEnd uintptr) {
|
func Kmain(multibootInfoPtr, kernelStart, kernelEnd uintptr) {
|
||||||
multiboot.SetInfoPtr(multibootInfoPtr)
|
multiboot.SetInfoPtr(multibootInfoPtr)
|
||||||
|
|
||||||
hal.InitTerminal()
|
|
||||||
hal.ActiveTerminal.Clear()
|
|
||||||
|
|
||||||
var err *kernel.Error
|
var err *kernel.Error
|
||||||
if err = allocator.Init(kernelStart, kernelEnd); err != nil {
|
if err = allocator.Init(kernelStart, kernelEnd); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -39,7 +37,10 @@ func Kmain(multibootInfoPtr, kernelStart, kernelEnd uintptr) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use kernel.Panic instead of panic to prevent the compiler from
|
// Detect and initialize hardware
|
||||||
|
hal.DetectHardware()
|
||||||
|
|
||||||
|
// Use kfmt.Panic instead of panic to prevent the compiler from
|
||||||
// treating kernel.Panic as dead-code and eliminating it.
|
// treating kernel.Panic as dead-code and eliminating it.
|
||||||
kernel.Panic(errKmainReturned)
|
kfmt.Panic(errKmainReturned)
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ package allocator
|
|||||||
import (
|
import (
|
||||||
"gopheros/kernel"
|
"gopheros/kernel"
|
||||||
"gopheros/kernel/hal/multiboot"
|
"gopheros/kernel/hal/multiboot"
|
||||||
"gopheros/kernel/kfmt/early"
|
"gopheros/kernel/kfmt"
|
||||||
"gopheros/kernel/mem"
|
"gopheros/kernel/mem"
|
||||||
"gopheros/kernel/mem/pmm"
|
"gopheros/kernel/mem/pmm"
|
||||||
"gopheros/kernel/mem/vmm"
|
"gopheros/kernel/mem/vmm"
|
||||||
@ -234,7 +234,7 @@ func (alloc *BitmapAllocator) reserveEarlyAllocatorFrames() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (alloc *BitmapAllocator) printStats() {
|
func (alloc *BitmapAllocator) printStats() {
|
||||||
early.Printf(
|
kfmt.Printf(
|
||||||
"[bitmap_alloc] page stats: free: %d/%d (%d reserved)\n",
|
"[bitmap_alloc] page stats: free: %d/%d (%d reserved)\n",
|
||||||
alloc.totalPages-alloc.reservedPages,
|
alloc.totalPages-alloc.reservedPages,
|
||||||
alloc.totalPages,
|
alloc.totalPages,
|
||||||
|
@ -406,7 +406,6 @@ func TestAllocatorPackageInit(t *testing.T) {
|
|||||||
return uintptr(unsafe.Pointer(&physMem[0])), nil
|
return uintptr(unsafe.Pointer(&physMem[0])), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
mockTTY()
|
|
||||||
if err := Init(0x100000, 0x1fa7c8); err != nil {
|
if err := Init(0x100000, 0x1fa7c8); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ package allocator
|
|||||||
import (
|
import (
|
||||||
"gopheros/kernel"
|
"gopheros/kernel"
|
||||||
"gopheros/kernel/hal/multiboot"
|
"gopheros/kernel/hal/multiboot"
|
||||||
"gopheros/kernel/kfmt/early"
|
"gopheros/kernel/kfmt"
|
||||||
"gopheros/kernel/mem"
|
"gopheros/kernel/mem"
|
||||||
"gopheros/kernel/mem/pmm"
|
"gopheros/kernel/mem/pmm"
|
||||||
)
|
)
|
||||||
@ -117,19 +117,19 @@ func (alloc *bootMemAllocator) AllocFrame() (pmm.Frame, *kernel.Error) {
|
|||||||
// printMemoryMap scans the memory region information provided by the
|
// printMemoryMap scans the memory region information provided by the
|
||||||
// bootloader and prints out the system's memory map.
|
// bootloader and prints out the system's memory map.
|
||||||
func (alloc *bootMemAllocator) printMemoryMap() {
|
func (alloc *bootMemAllocator) printMemoryMap() {
|
||||||
early.Printf("[boot_mem_alloc] system memory map:\n")
|
kfmt.Printf("[boot_mem_alloc] system memory map:\n")
|
||||||
var totalFree mem.Size
|
var totalFree mem.Size
|
||||||
multiboot.VisitMemRegions(func(region *multiboot.MemoryMapEntry) bool {
|
multiboot.VisitMemRegions(func(region *multiboot.MemoryMapEntry) bool {
|
||||||
early.Printf("\t[0x%10x - 0x%10x], size: %10d, type: %s\n", region.PhysAddress, region.PhysAddress+region.Length, region.Length, region.Type.String())
|
kfmt.Printf("\t[0x%10x - 0x%10x], size: %10d, type: %s\n", region.PhysAddress, region.PhysAddress+region.Length, region.Length, region.Type.String())
|
||||||
|
|
||||||
if region.Type == multiboot.MemAvailable {
|
if region.Type == multiboot.MemAvailable {
|
||||||
totalFree += mem.Size(region.Length)
|
totalFree += mem.Size(region.Length)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
early.Printf("[boot_mem_alloc] available memory: %dKb\n", uint64(totalFree/mem.Kb))
|
kfmt.Printf("[boot_mem_alloc] available memory: %dKb\n", uint64(totalFree/mem.Kb))
|
||||||
early.Printf("[boot_mem_alloc] kernel loaded at 0x%x - 0x%x\n", alloc.kernelStartAddr, alloc.kernelEndAddr)
|
kfmt.Printf("[boot_mem_alloc] kernel loaded at 0x%x - 0x%x\n", alloc.kernelStartAddr, alloc.kernelEndAddr)
|
||||||
early.Printf("[boot_mem_alloc] size: %d bytes, reserved pages: %d\n",
|
kfmt.Printf("[boot_mem_alloc] size: %d bytes, reserved pages: %d\n",
|
||||||
uint64(alloc.kernelEndAddr-alloc.kernelStartAddr),
|
uint64(alloc.kernelEndAddr-alloc.kernelStartAddr),
|
||||||
uint64(alloc.kernelEndFrame-alloc.kernelStartFrame+1),
|
uint64(alloc.kernelEndFrame-alloc.kernelStartFrame+1),
|
||||||
)
|
)
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package allocator
|
package allocator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gopheros/kernel/driver/video/console"
|
|
||||||
"gopheros/kernel/hal"
|
|
||||||
"gopheros/kernel/hal/multiboot"
|
"gopheros/kernel/hal/multiboot"
|
||||||
"testing"
|
"testing"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@ -118,13 +116,3 @@ var (
|
|||||||
24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func mockTTY() []byte {
|
|
||||||
// Mock a tty to handle early.Printf output
|
|
||||||
mockConsoleFb := make([]byte, 160*25)
|
|
||||||
mockConsole := &console.Ega{}
|
|
||||||
mockConsole.Init(80, 25, uintptr(unsafe.Pointer(&mockConsoleFb[0])))
|
|
||||||
hal.ActiveTerminal.AttachTo(mockConsole)
|
|
||||||
|
|
||||||
return mockConsoleFb
|
|
||||||
}
|
|
||||||
|
@ -61,12 +61,3 @@ func TestTranslateAmd64(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
phys, err := vmm.Translate(uintptr(100 * mem.Mb))
|
|
||||||
if err != nil {
|
|
||||||
early.Printf("err: %s\n", err.Error())
|
|
||||||
} else {
|
|
||||||
early.Printf("phys: 0x%x\n", phys)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"gopheros/kernel"
|
"gopheros/kernel"
|
||||||
"gopheros/kernel/cpu"
|
"gopheros/kernel/cpu"
|
||||||
"gopheros/kernel/irq"
|
"gopheros/kernel/irq"
|
||||||
"gopheros/kernel/kfmt/early"
|
"gopheros/kernel/kfmt"
|
||||||
"gopheros/kernel/mem"
|
"gopheros/kernel/mem"
|
||||||
"gopheros/kernel/mem/pmm"
|
"gopheros/kernel/mem/pmm"
|
||||||
)
|
)
|
||||||
@ -83,27 +83,27 @@ func pageFaultHandler(errorCode uint64, frame *irq.Frame, regs *irq.Regs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func nonRecoverablePageFault(faultAddress uintptr, errorCode uint64, frame *irq.Frame, regs *irq.Regs, err *kernel.Error) {
|
func nonRecoverablePageFault(faultAddress uintptr, errorCode uint64, frame *irq.Frame, regs *irq.Regs, err *kernel.Error) {
|
||||||
early.Printf("\nPage fault while accessing address: 0x%16x\nReason: ", faultAddress)
|
kfmt.Printf("\nPage fault while accessing address: 0x%16x\nReason: ", faultAddress)
|
||||||
switch {
|
switch {
|
||||||
case errorCode == 0:
|
case errorCode == 0:
|
||||||
early.Printf("read from non-present page")
|
kfmt.Printf("read from non-present page")
|
||||||
case errorCode == 1:
|
case errorCode == 1:
|
||||||
early.Printf("page protection violation (read)")
|
kfmt.Printf("page protection violation (read)")
|
||||||
case errorCode == 2:
|
case errorCode == 2:
|
||||||
early.Printf("write to non-present page")
|
kfmt.Printf("write to non-present page")
|
||||||
case errorCode == 3:
|
case errorCode == 3:
|
||||||
early.Printf("page protection violation (write)")
|
kfmt.Printf("page protection violation (write)")
|
||||||
case errorCode == 4:
|
case errorCode == 4:
|
||||||
early.Printf("page-fault in user-mode")
|
kfmt.Printf("page-fault in user-mode")
|
||||||
case errorCode == 8:
|
case errorCode == 8:
|
||||||
early.Printf("page table has reserved bit set")
|
kfmt.Printf("page table has reserved bit set")
|
||||||
case errorCode == 16:
|
case errorCode == 16:
|
||||||
early.Printf("instruction fetch")
|
kfmt.Printf("instruction fetch")
|
||||||
default:
|
default:
|
||||||
early.Printf("unknown")
|
kfmt.Printf("unknown")
|
||||||
}
|
}
|
||||||
|
|
||||||
early.Printf("\n\nRegisters:\n")
|
kfmt.Printf("\n\nRegisters:\n")
|
||||||
regs.Print()
|
regs.Print()
|
||||||
frame.Print()
|
frame.Print()
|
||||||
|
|
||||||
@ -112,8 +112,8 @@ func nonRecoverablePageFault(faultAddress uintptr, errorCode uint64, frame *irq.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func generalProtectionFaultHandler(_ uint64, frame *irq.Frame, regs *irq.Regs) {
|
func generalProtectionFaultHandler(_ uint64, frame *irq.Frame, regs *irq.Regs) {
|
||||||
early.Printf("\nGeneral protection fault while accessing address: 0x%x\n", readCR2Fn())
|
kfmt.Printf("\nGeneral protection fault while accessing address: 0x%x\n", readCR2Fn())
|
||||||
early.Printf("Registers:\n")
|
kfmt.Printf("Registers:\n")
|
||||||
regs.Print()
|
regs.Print()
|
||||||
frame.Print()
|
frame.Print()
|
||||||
|
|
||||||
|
@ -5,9 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"gopheros/kernel"
|
"gopheros/kernel"
|
||||||
"gopheros/kernel/cpu"
|
"gopheros/kernel/cpu"
|
||||||
"gopheros/kernel/driver/video/console"
|
|
||||||
"gopheros/kernel/hal"
|
|
||||||
"gopheros/kernel/irq"
|
"gopheros/kernel/irq"
|
||||||
|
"gopheros/kernel/kfmt"
|
||||||
"gopheros/kernel/mem"
|
"gopheros/kernel/mem"
|
||||||
"gopheros/kernel/mem/pmm"
|
"gopheros/kernel/mem/pmm"
|
||||||
"strings"
|
"strings"
|
||||||
@ -54,8 +53,6 @@ func TestRecoverablePageFault(t *testing.T) {
|
|||||||
{FlagPresent | FlagCopyOnWrite, nil, nil, false},
|
{FlagPresent | FlagCopyOnWrite, nil, nil, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
mockTTY()
|
|
||||||
|
|
||||||
ptePtrFn = func(entry uintptr) unsafe.Pointer { return unsafe.Pointer(&pageEntry) }
|
ptePtrFn = func(entry uintptr) unsafe.Pointer { return unsafe.Pointer(&pageEntry) }
|
||||||
readCR2Fn = func() uint64 { return uint64(uintptr(unsafe.Pointer(&origPage[0]))) }
|
readCR2Fn = func() uint64 { return uint64(uintptr(unsafe.Pointer(&origPage[0]))) }
|
||||||
unmapFn = func(_ Page) *kernel.Error { return nil }
|
unmapFn = func(_ Page) *kernel.Error { return nil }
|
||||||
@ -102,6 +99,10 @@ func TestRecoverablePageFault(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNonRecoverablePageFault(t *testing.T) {
|
func TestNonRecoverablePageFault(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
kfmt.SetOutputSink(nil)
|
||||||
|
}()
|
||||||
|
|
||||||
specs := []struct {
|
specs := []struct {
|
||||||
errCode uint64
|
errCode uint64
|
||||||
expReason string
|
expReason string
|
||||||
@ -143,19 +144,21 @@ func TestNonRecoverablePageFault(t *testing.T) {
|
|||||||
var (
|
var (
|
||||||
regs irq.Regs
|
regs irq.Regs
|
||||||
frame irq.Frame
|
frame irq.Frame
|
||||||
|
buf bytes.Buffer
|
||||||
)
|
)
|
||||||
|
|
||||||
|
kfmt.SetOutputSink(&buf)
|
||||||
for specIndex, spec := range specs {
|
for specIndex, spec := range specs {
|
||||||
t.Run(fmt.Sprint(specIndex), func(t *testing.T) {
|
t.Run(fmt.Sprint(specIndex), func(t *testing.T) {
|
||||||
|
buf.Reset()
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != errUnrecoverableFault {
|
if err := recover(); err != errUnrecoverableFault {
|
||||||
t.Errorf("expected a panic with errUnrecoverableFault; got %v", err)
|
t.Errorf("expected a panic with errUnrecoverableFault; got %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
fb := mockTTY()
|
|
||||||
|
|
||||||
nonRecoverablePageFault(0xbadf00d000, spec.errCode, &frame, ®s, errUnrecoverableFault)
|
nonRecoverablePageFault(0xbadf00d000, spec.errCode, &frame, ®s, errUnrecoverableFault)
|
||||||
if got := readTTY(fb); !strings.Contains(got, spec.expReason) {
|
if got := buf.String(); !strings.Contains(got, spec.expReason) {
|
||||||
t.Errorf("expected reason %q; got output:\n%q", spec.expReason, got)
|
t.Errorf("expected reason %q; got output:\n%q", spec.expReason, got)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -182,7 +185,6 @@ func TestGPtHandler(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
mockTTY()
|
|
||||||
generalProtectionFaultHandler(0, &frame, ®s)
|
generalProtectionFaultHandler(0, &frame, ®s)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -252,30 +254,3 @@ func TestInit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func readTTY(fb []byte) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
for i := 0; i < len(fb); i += 2 {
|
|
||||||
ch := fb[i]
|
|
||||||
if ch == 0 {
|
|
||||||
if i+2 < len(fb) && fb[i+2] != 0 {
|
|
||||||
buf.WriteByte('\n')
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteByte(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func mockTTY() []byte {
|
|
||||||
// Mock a tty to handle early.Printf output
|
|
||||||
mockConsoleFb := make([]byte, 160*25)
|
|
||||||
mockConsole := &console.Ega{}
|
|
||||||
mockConsole.Init(80, 25, uintptr(unsafe.Pointer(&mockConsoleFb[0])))
|
|
||||||
hal.ActiveTerminal.AttachTo(mockConsole)
|
|
||||||
|
|
||||||
return mockConsoleFb
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user