diff --git a/src/gopheros/device/driver.go b/src/gopheros/device/driver.go index 11cab21..aec2c38 100644 --- a/src/gopheros/device/driver.go +++ b/src/gopheros/device/driver.go @@ -13,3 +13,7 @@ type Driver interface { // 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 diff --git a/src/gopheros/device/tty/device.go b/src/gopheros/device/tty/device.go index a0f4e4d..d2f87a3 100644 --- a/src/gopheros/device/tty/device.go +++ b/src/gopheros/device/tty/device.go @@ -5,6 +5,14 @@ import ( "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 diff --git a/src/gopheros/device/tty/probe.go b/src/gopheros/device/tty/probe.go new file mode 100644 index 0000000..6911b0b --- /dev/null +++ b/src/gopheros/device/tty/probe.go @@ -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, + } +} diff --git a/src/gopheros/device/tty/vt.go b/src/gopheros/device/tty/vt.go index ea63238..fe4dd41 100644 --- a/src/gopheros/device/tty/vt.go +++ b/src/gopheros/device/tty/vt.go @@ -1,6 +1,7 @@ package tty import ( + "gopheros/device" "gopheros/device/video/console" "gopheros/kernel" "io" @@ -258,3 +259,7 @@ func (t *VT) DriverVersion() (uint16, uint16, uint16) { // DriverInit initializes this driver. func (t *VT) DriverInit() *kernel.Error { return nil } + +func probeForVT() device.Driver { + return NewVT(DefaultTabWidth, DefaultScrollback) +} diff --git a/src/gopheros/device/tty/vt_test.go b/src/gopheros/device/tty/vt_test.go index 678acba..9f95280 100644 --- a/src/gopheros/device/tty/vt_test.go +++ b/src/gopheros/device/tty/vt_test.go @@ -5,6 +5,7 @@ import ( "gopheros/device/video/console" "image/color" "io" + "reflect" "testing" ) @@ -337,6 +338,28 @@ func TestVTDriverInterface(t *testing.T) { } } +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 diff --git a/src/gopheros/device/video/console/probe.go b/src/gopheros/device/video/console/probe.go new file mode 100644 index 0000000..a2a47d3 --- /dev/null +++ b/src/gopheros/device/video/console/probe.go @@ -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, + } +} diff --git a/src/gopheros/device/video/console/vga_text.go b/src/gopheros/device/video/console/vga_text.go index 76aecbf..c058635 100644 --- a/src/gopheros/device/video/console/vga_text.go +++ b/src/gopheros/device/video/console/vga_text.go @@ -1,8 +1,10 @@ package console import ( + "gopheros/device" "gopheros/kernel" "gopheros/kernel/cpu" + "gopheros/kernel/hal/multiboot" "image/color" "reflect" "unsafe" @@ -196,3 +198,15 @@ func (cons *VgaTextConsole) DriverVersion() (uint16, uint16, uint16) { // 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 +} diff --git a/src/gopheros/device/video/console/vga_text_test.go b/src/gopheros/device/video/console/vga_text_test.go index 9213299..d852fb6 100644 --- a/src/gopheros/device/video/console/vga_text_test.go +++ b/src/gopheros/device/video/console/vga_text_test.go @@ -3,7 +3,9 @@ package console import ( "gopheros/device" "gopheros/kernel/cpu" + "gopheros/kernel/hal/multiboot" "image/color" + "reflect" "testing" "unsafe" ) @@ -318,3 +320,39 @@ func TestVgaTextDriverInterface(t *testing.T) { 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") + } +} diff --git a/src/gopheros/kernel/hal/hal.go b/src/gopheros/kernel/hal/hal.go index 36ea7e0..036be4e 100644 --- a/src/gopheros/kernel/hal/hal.go +++ b/src/gopheros/kernel/hal/hal.go @@ -1,23 +1,67 @@ package hal import ( - "gopheros/kernel/driver/tty" - "gopheros/kernel/driver/video/console" - "gopheros/kernel/hal/multiboot" + "gopheros/device" + "gopheros/device/tty" + "gopheros/device/video/console" + "gopheros/kernel/kfmt" ) -var ( - egaConsole = &console.Ega{} - - // ActiveTerminal points to the currently active terminal. - ActiveTerminal = &tty.Vt{} -) - -// InitTerminal provides a basic terminal to allow the kernel to emit some output -// till everything is properly setup -func InitTerminal() { - fbInfo := multiboot.GetFramebufferInfo() - - egaConsole.Init(uint16(fbInfo.Width), uint16(fbInfo.Height), uintptr(fbInfo.PhysAddr)) - ActiveTerminal.AttachTo(egaConsole) +// managedDevices contains the devices discovered by the HAL. +type managedDevices struct { + activeConsole console.Device + activeTTY tty.Device +} + +var devices managedDevices + +// ActiveTTY returns the currently active TTY +func ActiveTTY() tty.Device { + return devices.activeTTY +} + +// 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 } diff --git a/src/gopheros/kernel/kmain/kmain.go b/src/gopheros/kernel/kmain/kmain.go index c39861e..7274f59 100644 --- a/src/gopheros/kernel/kmain/kmain.go +++ b/src/gopheros/kernel/kmain/kmain.go @@ -5,6 +5,7 @@ import ( "gopheros/kernel/goruntime" "gopheros/kernel/hal" "gopheros/kernel/hal/multiboot" + "gopheros/kernel/kfmt" "gopheros/kernel/mem/pmm/allocator" "gopheros/kernel/mem/vmm" ) @@ -27,9 +28,6 @@ var ( func Kmain(multibootInfoPtr, kernelStart, kernelEnd uintptr) { multiboot.SetInfoPtr(multibootInfoPtr) - hal.InitTerminal() - hal.ActiveTerminal.Clear() - var err *kernel.Error if err = allocator.Init(kernelStart, kernelEnd); err != nil { panic(err) @@ -39,7 +37,10 @@ func Kmain(multibootInfoPtr, kernelStart, kernelEnd uintptr) { 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. - kernel.Panic(errKmainReturned) + kfmt.Panic(errKmainReturned) }