diff --git a/src/gopheros/device/video/console/probe.go b/src/gopheros/device/video/console/probe.go index 1981378..1937343 100644 --- a/src/gopheros/device/video/console/probe.go +++ b/src/gopheros/device/video/console/probe.go @@ -1,9 +1,15 @@ package console -import "gopheros/device" -import "gopheros/kernel/hal/multiboot" +import ( + "gopheros/device" + "gopheros/kernel/cpu" + "gopheros/kernel/hal/multiboot" + "gopheros/kernel/mem/vmm" +) var ( + mapRegionFn = vmm.MapRegion + portWriteByteFn = cpu.PortWriteByte getFramebufferInfoFn = multiboot.GetFramebufferInfo // ProbeFuncs is a slice of device probe functions that is used by diff --git a/src/gopheros/device/video/console/vga_text.go b/src/gopheros/device/video/console/vga_text.go index f5be66b..0215363 100644 --- a/src/gopheros/device/video/console/vga_text.go +++ b/src/gopheros/device/video/console/vga_text.go @@ -3,16 +3,17 @@ package console import ( "gopheros/device" "gopheros/kernel" - "gopheros/kernel/cpu" "gopheros/kernel/hal/multiboot" + "gopheros/kernel/kfmt" + "gopheros/kernel/mem" + "gopheros/kernel/mem/pmm" + "gopheros/kernel/mem/vmm" "image/color" "io" "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. @@ -28,7 +29,8 @@ type VgaTextConsole struct { width uint32 height uint32 - fb []uint16 + fbPhysAddr uintptr + fb []uint16 palette color.Palette defaultFg uint8 @@ -40,15 +42,10 @@ type VgaTextConsole struct { // framebuffer mapped to fbPhysAddr. func NewVgaTextConsole(columns, rows uint32, 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: int(columns * rows), - Cap: int(columns * rows), - Data: fbPhysAddr, - })), + width: columns, + height: rows, + fbPhysAddr: fbPhysAddr, + clearChar: uint16(' '), palette: color.Palette{ color.RGBA{R: 0, G: 0, B: 1}, /* black */ color.RGBA{R: 0, G: 0, B: 128}, /* blue */ @@ -198,7 +195,29 @@ func (cons *VgaTextConsole) DriverVersion() (uint16, uint16, uint16) { } // DriverInit initializes this driver. -func (cons *VgaTextConsole) DriverInit(_ io.Writer) *kernel.Error { return nil } +func (cons *VgaTextConsole) DriverInit(w io.Writer) *kernel.Error { + // Map the framebuffer so we can write to it + fbSize := mem.Size(cons.width * cons.height * 2) + fbPage, err := mapRegionFn( + pmm.Frame(cons.fbPhysAddr>>mem.PageShift), + fbSize, + vmm.FlagPresent|vmm.FlagRW, + ) + + if err != nil { + return err + } + + cons.fb = *(*[]uint16)(unsafe.Pointer(&reflect.SliceHeader{ + Len: int(fbSize >> 1), + Cap: int(fbSize >> 1), + Data: fbPage.Address(), + })) + + kfmt.Fprintf(w, "mapped framebuffer to 0x%x\n", fbPage.Address()) + + return nil +} // probeForVgaTextConsole checks for the presence of a vga text console. func probeForVgaTextConsole() device.Driver { diff --git a/src/gopheros/device/video/console/vga_text_test.go b/src/gopheros/device/video/console/vga_text_test.go index 3d5cb25..d18b9be 100644 --- a/src/gopheros/device/video/console/vga_text_test.go +++ b/src/gopheros/device/video/console/vga_text_test.go @@ -2,8 +2,12 @@ package console import ( "gopheros/device" + "gopheros/kernel" "gopheros/kernel/cpu" "gopheros/kernel/hal/multiboot" + "gopheros/kernel/mem" + "gopheros/kernel/mem/pmm" + "gopheros/kernel/mem/vmm" "image/color" "testing" "unsafe" @@ -63,6 +67,7 @@ func TestVgaTextFill(t *testing.T) { fb := make([]uint16, 80*25) cons := NewVgaTextConsole(80, 25, uintptr(unsafe.Pointer(&fb[0]))) + cons.fb = fb cw, ch := cons.Dimensions() testPat := uint16(0xDEAD) @@ -101,6 +106,7 @@ nextSpec: func TestVgaTextScroll(t *testing.T) { fb := make([]uint16, 80*25) cons := NewVgaTextConsole(80, 25, uintptr(unsafe.Pointer(&fb[0]))) + cons.fb = fb cw, ch := cons.Dimensions() t.Run("up", func(t *testing.T) { @@ -176,6 +182,7 @@ func TestVgaTextScroll(t *testing.T) { func TestVgaTextWrite(t *testing.T) { fb := make([]uint16, 80*25) cons := NewVgaTextConsole(80, 25, uintptr(unsafe.Pointer(&fb[0]))) + cons.fb = fb defaultFg, defaultBg := cons.DefaultColors() t.Run("off-screen", func(t *testing.T) { @@ -309,12 +316,11 @@ func TestVgaTextSetPaletteColor(t *testing.T) { } func TestVgaTextDriverInterface(t *testing.T) { + defer func() { + mapRegionFn = vmm.MapRegion + }() var dev device.Driver = NewVgaTextConsole(80, 25, 0) - if err := dev.DriverInit(nil); err != nil { - t.Fatal(err) - } - if dev.DriverName() == "" { t.Fatal("DriverName() returned an empty string") } @@ -322,6 +328,27 @@ func TestVgaTextDriverInterface(t *testing.T) { if major, minor, patch := dev.DriverVersion(); major+minor+patch == 0 { t.Fatal("DriverVersion() returned an invalid version number") } + + t.Run("init success", func(t *testing.T) { + mapRegionFn = func(_ pmm.Frame, _ mem.Size, _ vmm.PageTableEntryFlag) (vmm.Page, *kernel.Error) { + return 0xb8000, nil + } + + if err := dev.DriverInit(nil); err != nil { + t.Fatal(err) + } + }) + + t.Run("init fail", func(t *testing.T) { + expErr := &kernel.Error{Module: "test", Message: "something went wrong"} + mapRegionFn = func(_ pmm.Frame, _ mem.Size, _ vmm.PageTableEntryFlag) (vmm.Page, *kernel.Error) { + return 0, expErr + } + + if err := dev.DriverInit(nil); err != expErr { + t.Fatalf("expected error: %v; got %v", expErr, err) + } + }) } func TestVgaTextProbe(t *testing.T) { diff --git a/src/gopheros/kernel/mem/vmm/map.go b/src/gopheros/kernel/mem/vmm/map.go index 1edaa5a..fe56f3b 100644 --- a/src/gopheros/kernel/mem/vmm/map.go +++ b/src/gopheros/kernel/mem/vmm/map.go @@ -47,6 +47,8 @@ var ( // which will cause a fault if called in user-mode. flushTLBEntryFn = cpu.FlushTLBEntry + earlyReserveRegionFn = EarlyReserveRegion + errNoHugePageSupport = &kernel.Error{Module: "vmm", Message: "huge pages are not supported"} errAttemptToRWMapReservedFrame = &kernel.Error{Module: "vmm", Message: "reserved blank frame cannot be mapped with a RW flag"} ) @@ -105,6 +107,29 @@ func Map(page Page, frame pmm.Frame, flags PageTableEntryFlag) *kernel.Error { return err } +// MapRegion establishes a mapping to the physical memory region which starts +// at the given frame and ends at frame + pages(size). The size argument is +// always rounded up to the nearest page boundary. MapRegion reserves the next +// available region in the active virtual address space, establishes the +// mapping and returns back the Page that corresponds to the region start. +func MapRegion(frame pmm.Frame, size mem.Size, flags PageTableEntryFlag) (Page, *kernel.Error) { + // Reserve next free block in the address space + size = (size + (mem.PageSize - 1)) & ^(mem.PageSize - 1) + startPage, err := earlyReserveRegionFn(size) + if err != nil { + return 0, err + } + + pageCount := size >> mem.PageShift + for page := PageFromAddress(startPage); pageCount > 0; pageCount, page, frame = pageCount-1, page+1, frame+1 { + if err := mapFn(page, frame, flags); err != nil { + return 0, err + } + } + + return PageFromAddress(startPage), nil +} + // MapTemporary establishes a temporary RW mapping of a physical memory frame // to a fixed virtual address overwriting any previous mapping. The temporary // mapping mechanism is primarily used by the kernel to access and initialize diff --git a/src/gopheros/kernel/mem/vmm/map_test.go b/src/gopheros/kernel/mem/vmm/map_test.go index 70116c1..7a0d0f8 100644 --- a/src/gopheros/kernel/mem/vmm/map_test.go +++ b/src/gopheros/kernel/mem/vmm/map_test.go @@ -97,6 +97,73 @@ func TestMapTemporaryAmd64(t *testing.T) { } } +func TestMapRegion(t *testing.T) { + defer func() { + mapFn = Map + earlyReserveRegionFn = EarlyReserveRegion + }() + + t.Run("success", func(t *testing.T) { + mapCallCount := 0 + mapFn = func(_ Page, _ pmm.Frame, flags PageTableEntryFlag) *kernel.Error { + mapCallCount++ + return nil + } + + earlyReserveRegionCallCount := 0 + earlyReserveRegionFn = func(_ mem.Size) (uintptr, *kernel.Error) { + earlyReserveRegionCallCount++ + return 0xf00, nil + } + + if _, err := MapRegion(pmm.Frame(0xdf0000), 4097, FlagPresent|FlagRW); err != nil { + t.Fatal(err) + } + + if exp := 2; mapCallCount != exp { + t.Errorf("expected Map to be called %d time(s); got %d", exp, mapCallCount) + } + + if exp := 1; earlyReserveRegionCallCount != exp { + t.Errorf("expected EarlyReserveRegion to be called %d time(s); got %d", exp, earlyReserveRegionCallCount) + } + }) + + t.Run("EarlyReserveRegion fails", func(t *testing.T) { + expErr := &kernel.Error{Module: "test", Message: "out of address space"} + + earlyReserveRegionFn = func(_ mem.Size) (uintptr, *kernel.Error) { + return 0, expErr + } + + if _, err := MapRegion(pmm.Frame(0xdf0000), 128000, FlagPresent|FlagRW); err != expErr { + t.Fatalf("expected error: %v; got %v", expErr, err) + } + }) + + t.Run("Map fails", func(t *testing.T) { + expErr := &kernel.Error{Module: "test", Message: "map failed"} + + earlyReserveRegionCallCount := 0 + earlyReserveRegionFn = func(_ mem.Size) (uintptr, *kernel.Error) { + earlyReserveRegionCallCount++ + return 0xf00, nil + } + + mapFn = func(_ Page, _ pmm.Frame, flags PageTableEntryFlag) *kernel.Error { + return expErr + } + + if _, err := MapRegion(pmm.Frame(0xdf0000), 128000, FlagPresent|FlagRW); err != expErr { + t.Fatalf("expected error: %v; got %v", expErr, err) + } + + if exp := 1; earlyReserveRegionCallCount != exp { + t.Errorf("expected EarlyReserveRegion to be called %d time(s); got %d", exp, earlyReserveRegionCallCount) + } + }) +} + func TestMapTemporaryErrorsAmd64(t *testing.T) { if runtime.GOARCH != "amd64" { t.Skip("test requires amd64 runtime; skipping")