diff --git a/kernel/goruntime/bootstrap.go b/kernel/goruntime/bootstrap.go index 981fb28..8e3b63f 100644 --- a/kernel/goruntime/bootstrap.go +++ b/kernel/goruntime/bootstrap.go @@ -6,12 +6,14 @@ import ( "unsafe" "github.com/achilleasa/gopher-os/kernel/mem" + "github.com/achilleasa/gopher-os/kernel/mem/pmm/allocator" "github.com/achilleasa/gopher-os/kernel/mem/vmm" ) var ( mapFn = vmm.Map earlyReserveRegionFn = vmm.EarlyReserveRegion + frameAllocFn = allocator.AllocFrame ) //go:linkname mSysStatInc runtime.mSysStatInc @@ -65,6 +67,39 @@ func sysMap(virtAddr unsafe.Pointer, size uintptr, reserved bool, sysStat *uint6 return unsafe.Pointer(regionStartAddr) } +// sysAlloc reserves enough phsysical frames to satisfy the allocation request +// and establishes a contiguous virtual page mapping for them returning back +// the pointer to the virtual region start. +// +// This function replaces runtime.sysMap and is required for initializing the +// Go allocator. +// +//go:redirect-from runtime.sysAlloc +//go:nosplit +func sysAlloc(size uintptr, sysStat *uint64) unsafe.Pointer { + regionSize := (mem.Size(size) + mem.PageSize - 1) & ^(mem.PageSize - 1) + regionStartAddr, err := earlyReserveRegionFn(regionSize) + if err != nil { + return unsafe.Pointer(uintptr(0)) + } + + mapFlags := vmm.FlagPresent | vmm.FlagNoExecute | vmm.FlagRW + pageCount := regionSize >> mem.PageShift + for page := vmm.PageFromAddress(regionStartAddr); pageCount > 0; pageCount, page = pageCount-1, page+1 { + frame, err := frameAllocFn() + if err != nil { + return unsafe.Pointer(uintptr(0)) + } + + if err = mapFn(page, frame, mapFlags); err != nil { + return unsafe.Pointer(uintptr(0)) + } + } + + mSysStatInc(sysStat, uintptr(regionSize)) + return unsafe.Pointer(regionStartAddr) +} + func init() { // Dummy calls so the compiler does not optimize away the functions in // this file. @@ -76,4 +111,5 @@ func init() { sysReserve(zeroPtr, 0, &reserved) sysMap(zeroPtr, 0, reserved, &stat) + sysAlloc(0, &stat) } diff --git a/kernel/goruntime/bootstrap_test.go b/kernel/goruntime/bootstrap_test.go index 45d6148..603f123 100644 --- a/kernel/goruntime/bootstrap_test.go +++ b/kernel/goruntime/bootstrap_test.go @@ -7,6 +7,7 @@ import ( "github.com/achilleasa/gopher-os/kernel" "github.com/achilleasa/gopher-os/kernel/mem" "github.com/achilleasa/gopher-os/kernel/mem/pmm" + "github.com/achilleasa/gopher-os/kernel/mem/pmm/allocator" "github.com/achilleasa/gopher-os/kernel/mem/vmm" ) @@ -130,3 +131,107 @@ func TestSysMap(t *testing.T) { sysMap(nil, 0, false, nil) }) } + +func TestSysAlloc(t *testing.T) { + defer func() { + earlyReserveRegionFn = vmm.EarlyReserveRegion + mapFn = vmm.Map + frameAllocFn = allocator.AllocFrame + }() + + t.Run("success", func(t *testing.T) { + specs := []struct { + reqSize mem.Size + expMapCallCount int + }{ + // exact multiple of page size + {4 * mem.PageSize, 4}, + // round up to nearest page size + {(4 * mem.PageSize) + 1, 5}, + } + + expRegionStartAddr := uintptr(10 * mem.PageSize) + earlyReserveRegionFn = func(_ mem.Size) (uintptr, *kernel.Error) { + return expRegionStartAddr, nil + } + + frameAllocFn = func() (pmm.Frame, *kernel.Error) { + return pmm.Frame(0), nil + } + + for specIndex, spec := range specs { + var ( + sysStat uint64 + mapCallCount int + ) + + mapFn = func(_ vmm.Page, _ pmm.Frame, flags vmm.PageTableEntryFlag) *kernel.Error { + expFlags := vmm.FlagPresent | vmm.FlagNoExecute | vmm.FlagRW + if flags != expFlags { + t.Errorf("[spec %d] expected map flags to be %d; got %d", specIndex, expFlags, flags) + } + mapCallCount++ + return nil + } + + if got := sysAlloc(uintptr(spec.reqSize), &sysStat); uintptr(got) != expRegionStartAddr { + t.Errorf("[spec %d] expected sysAlloc to return address 0x%x; got 0x%x", specIndex, expRegionStartAddr, uintptr(got)) + } + + if mapCallCount != spec.expMapCallCount { + t.Errorf("[spec %d] expected vmm.Map call count to be %d; got %d", specIndex, spec.expMapCallCount, mapCallCount) + } + + if exp := uint64(spec.expMapCallCount << mem.PageShift); sysStat != exp { + t.Errorf("[spec %d] expected stat counter to be %d; got %d", specIndex, exp, sysStat) + } + } + }) + + t.Run("earlyReserveRegion fails", func(t *testing.T) { + earlyReserveRegionFn = func(rsvSize mem.Size) (uintptr, *kernel.Error) { + return 0, &kernel.Error{Module: "test", Message: "consumed available address space"} + } + + var sysStat uint64 + if got := sysAlloc(1, &sysStat); got != unsafe.Pointer(uintptr(0)) { + t.Fatalf("expected sysAlloc to return 0x0 if EarlyReserveRegion returns an error; got 0x%x", uintptr(got)) + } + }) + + t.Run("frame allocation fails", func(t *testing.T) { + expRegionStartAddr := uintptr(10 * mem.PageSize) + earlyReserveRegionFn = func(rsvSize mem.Size) (uintptr, *kernel.Error) { + return expRegionStartAddr, nil + } + + frameAllocFn = func() (pmm.Frame, *kernel.Error) { + return pmm.InvalidFrame, &kernel.Error{Module: "test", Message: "out of memory"} + } + + var sysStat uint64 + if got := sysAlloc(1, &sysStat); got != unsafe.Pointer(uintptr(0)) { + t.Fatalf("expected sysAlloc to return 0x0 if AllocFrame returns an error; got 0x%x", uintptr(got)) + } + }) + + t.Run("map fails", func(t *testing.T) { + expRegionStartAddr := uintptr(10 * mem.PageSize) + earlyReserveRegionFn = func(rsvSize mem.Size) (uintptr, *kernel.Error) { + return expRegionStartAddr, nil + } + + frameAllocFn = func() (pmm.Frame, *kernel.Error) { + return pmm.Frame(0), nil + } + + mapFn = func(_ vmm.Page, _ pmm.Frame, _ vmm.PageTableEntryFlag) *kernel.Error { + return &kernel.Error{Module: "test", Message: "map failed"} + } + + var sysStat uint64 + if got := sysAlloc(1, &sysStat); got != unsafe.Pointer(uintptr(0)) { + t.Fatalf("expected sysAlloc to return 0x0 if AllocFrame returns an error; got 0x%x", uintptr(got)) + } + }) +}