From 8ac2ba82cc271bc55732a524f17e9de867a5a4a5 Mon Sep 17 00:00:00 2001 From: Achilleas Anagnostopoulos Date: Sat, 8 Jul 2017 16:04:39 +0100 Subject: [PATCH 1/2] Implement function for mapping a contiguous physical memory region The MapRegion function can be used by device drivers to ensure that they can access memory mapped by the various devices. --- src/gopheros/kernel/mem/vmm/map.go | 25 +++++++++ src/gopheros/kernel/mem/vmm/map_test.go | 67 +++++++++++++++++++++++++ 2 files changed, 92 insertions(+) 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") From 13ef4cd08d450f13101352f3d64a5a79fa41fce1 Mon Sep 17 00:00:00 2001 From: Achilleas Anagnostopoulos Date: Sat, 8 Jul 2017 20:38:14 +0100 Subject: [PATCH 2/2] Map physical address of the vga text console framebuffer in DriverInit Currently, the kernel can write to 0xb80000 because this is part of the initial identify mapping set up by the rt0 code. When we establish new mappings for the kernel using its real VMA address then writes to the framebuffer will cause a page fault unless we explicitly map it. --- src/gopheros/device/video/console/probe.go | 10 +++- src/gopheros/device/video/console/vga_text.go | 47 +++++++++++++------ .../device/video/console/vga_text_test.go | 35 ++++++++++++-- 3 files changed, 72 insertions(+), 20 deletions(-) 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) {