From d4c0d5237292b1c562fc01bd3bc106bb6c281ed5 Mon Sep 17 00:00:00 2001 From: Achilleas Anagnostopoulos Date: Tue, 28 Mar 2017 08:18:17 +0100 Subject: [PATCH 1/5] Implement multiboot info structure tag scanner --- kernel/multiboot/multiboot.go | 115 +++++++++++++++++++++++++ kernel/multiboot/multiboot_test.go | 132 +++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+) create mode 100644 kernel/multiboot/multiboot.go create mode 100644 kernel/multiboot/multiboot_test.go diff --git a/kernel/multiboot/multiboot.go b/kernel/multiboot/multiboot.go new file mode 100644 index 0000000..84b84eb --- /dev/null +++ b/kernel/multiboot/multiboot.go @@ -0,0 +1,115 @@ +package multiboot + +import "unsafe" + +type tagType uint32 + +const ( + tagMbSectionEnd tagType = iota + tagBootCmdLine + tagBootLoaderName + tagModules + tagBasicMemoryInfo + tagBiosBootDevice + tagMemoryMap + tagVbeInfo + tagFramebufferInfo + tagElfSymbols + tagApmTable +) + +// info describes the multiboot info section header. +type info struct { + // Total size of multiboot info section. + totalSize uint32 + + // Always set to zero; reserved for future use + reserved uint32 +} + +// tagHeader describes the header the preceedes each tag. +type tagHeader struct { + // The type of the tag + tagType tagType + + // The size of the tag including the header but *not* including any + // padding. According to the spec, each tag starts at a 8-byte aligned + // address. + size uint32 +} + +// mmapHeader describes the header for a memory map specification. +type mmapHeader struct { + // The size of each entry. + entrySize uint32 + + // The version of the entries that follow. + entryVersion uint32 +} + +// MemoryEntryType defines the type of a MemoryMapEntry. +type MemoryEntryType uint32 + +const ( + // MemAvailable indicates that the memory region is available for use. + MemAvailable MemoryEntryType = iota + 1 + + // MemReserved indicates that the memory region is not available for use. + MemReserved + + // MemAcpiReclaimable indicates a memory region that holds ACPI info that + // can be reused by the OS. + MemAcpiReclaimable + + // MemNvs indicates memory that must be preserved when hibernating. + MemNvs + + // Any value >= memUnknown will be mapped to MemReserved. + memUnknown +) + +/// MemoryMapEntry describes a memory region entry, namely its physical address, +// its length and its type. +type MemoryMapEntry struct { + // The physical address for this memory region. + PhysAddress uint64 + + // The length of the memory region. + Length uint64 + + // The type of this entry. + Type MemoryEntryType +} + +var ( + infoData uintptr +) + +// SetInfoPtr updates the internal multiboot information pointer to the given +// value. This function must be invoked before invoking any other function +// exported by this package. +func SetInfoPtr(ptr uintptr) { + infoData = ptr +} + +// findTagByType scans the multiboot info data looking for the start of of the +// specified type. It returns a pointer to the tag contents start offset and +// the content length exluding the tag header. +// +// If the tag is not present in the multiboot info, findTagSection will return +// back (0,0). +func findTagByType(tagType tagType) (uintptr, uint32) { + var ptrTagHeader *tagHeader + + curPtr := infoData + 8 + for ptrTagHeader = (*tagHeader)(unsafe.Pointer(curPtr)); ptrTagHeader.tagType != tagMbSectionEnd; ptrTagHeader = (*tagHeader)(unsafe.Pointer(curPtr)) { + if ptrTagHeader.tagType == tagType { + return curPtr + 8, ptrTagHeader.size - 8 + } + + // Tags are aligned at 8-byte aligned addresses + curPtr += uintptr(int32(ptrTagHeader.size+7) & ^7) + } + + return 0, 0 +} diff --git a/kernel/multiboot/multiboot_test.go b/kernel/multiboot/multiboot_test.go new file mode 100644 index 0000000..f97991a --- /dev/null +++ b/kernel/multiboot/multiboot_test.go @@ -0,0 +1,132 @@ +package multiboot + +import ( + "testing" + "unsafe" +) + +func TestFindTagByType(t *testing.T) { + specs := []struct { + tagType tagType + expSize uint32 + }{ + {tagBootCmdLine, 1}, + {tagBootLoaderName, 27}, + {tagBasicMemoryInfo, 8}, + {tagBiosBootDevice, 12}, + {tagMemoryMap, 152}, + {tagFramebufferInfo, 24}, + {tagElfSymbols, 972}, + {tagApmTable, 20}, + } + + SetInfoPtr(uintptr(unsafe.Pointer(&multibootInfoTestData[0]))) + + for specIndex, spec := range specs { + _, size := findTagByType(spec.tagType) + + if size != spec.expSize { + t.Errorf("[spec %d] expected tag size for tag type %d to be %d; got %d", specIndex, spec.tagType, spec.expSize, size) + + } + } +} + +func TestFindTagByTypeWithMissingTag(t *testing.T) { + SetInfoPtr(uintptr(unsafe.Pointer(&multibootInfoTestData[0]))) + + if offset, size := findTagByType(tagModules); offset != 0 || size != 0 { + t.Fatalf("expected findTagByType to return (0,0) for missing tag; got (%d, %d)", offset, size) + } +} + +var ( + // A dump of multiboot data when running under qemu. + multibootInfoTestData = []byte{ + 72, 5, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 9, 0, 0, 0, + 0, 171, 253, 7, 118, 119, 123, 0, 2, 0, 0, 0, 35, 0, 0, 0, + 71, 82, 85, 66, 32, 50, 46, 48, 50, 126, 98, 101, 116, 97, 50, 45, + 57, 117, 98, 117, 110, 116, 117, 49, 46, 54, 0, 0, 0, 0, 0, 0, + 10, 0, 0, 0, 28, 0, 0, 0, 2, 1, 0, 240, 4, 213, 0, 0, + 0, 240, 0, 240, 3, 0, 240, 255, 240, 255, 240, 255, 0, 0, 0, 0, + 6, 0, 0, 0, 160, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 252, 9, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 252, 9, 0, 0, 0, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, + 0, 0, 238, 7, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 254, 7, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252, 255, 0, 0, 0, 0, + 0, 0, 4, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 9, 0, 0, 0, 212, 3, 0, 0, 24, 0, 0, 0, 40, 0, 0, 0, + 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 0, 0, 0, + 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 16, 0, 0, 16, 0, 0, + 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 38, 0, 0, 0, 1, 0, 0, 0, 6, 0, 0, 0, + 0, 16, 16, 0, 0, 32, 0, 0, 135, 26, 4, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, + 1, 0, 0, 0, 2, 0, 0, 0, 0, 48, 20, 0, 0, 64, 4, 0, + 194, 167, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, + 0, 0, 0, 0, 52, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, + 224, 215, 21, 0, 224, 231, 5, 0, 176, 6, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, + 1, 0, 0, 0, 2, 0, 0, 0, 144, 222, 21, 0, 144, 238, 5, 0, + 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 72, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, + 160, 222, 21, 0, 160, 238, 5, 0, 119, 23, 2, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 83, 0, 0, 0, + 7, 0, 0, 0, 2, 0, 0, 0, 32, 246, 23, 0, 32, 6, 8, 0, + 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, + 0, 0, 0, 0, 100, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, + 0, 0, 24, 0, 0, 16, 8, 0, 204, 5, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 106, 0, 0, 0, + 1, 0, 0, 0, 3, 0, 0, 0, 224, 5, 24, 0, 224, 21, 8, 0, + 178, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, + 0, 0, 0, 0, 117, 0, 0, 0, 8, 0, 0, 0, 3, 4, 0, 0, + 148, 15, 24, 0, 146, 31, 8, 0, 4, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 123, 0, 0, 0, + 8, 0, 0, 0, 3, 0, 0, 0, 0, 16, 24, 0, 146, 31, 8, 0, + 176, 61, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, + 0, 0, 0, 0, 128, 0, 0, 0, 8, 0, 0, 0, 3, 0, 0, 0, + 192, 77, 25, 0, 146, 31, 8, 0, 32, 56, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 138, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 224, 133, 25, 0, 146, 31, 8, 0, + 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 153, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 32, 134, 25, 0, 210, 31, 8, 0, 129, 26, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 169, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 161, 160, 25, 0, 83, 58, 8, 0, + 2, 201, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 181, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 163, 105, 27, 0, 85, 3, 10, 0, 25, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 195, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 188, 106, 27, 0, 110, 4, 10, 0, + 67, 153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 207, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 4, 28, 0, 184, 157, 10, 0, 252, 112, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 252, 116, 28, 0, 180, 14, 11, 0, + 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 231, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 12, 117, 28, 0, 196, 14, 11, 0, 239, 79, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 251, 196, 28, 0, 179, 94, 11, 0, + 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 244, 197, 28, 0, 108, 99, 11, 0, 80, 77, 0, 0, 23, 0, 0, 0, + 210, 4, 0, 0, 4, 0, 0, 0, 16, 0, 0, 0, 9, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 68, 19, 29, 0, 188, 176, 11, 0, + 107, 104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 16, 0, 0, 0, + 127, 2, 0, 0, 128, 251, 1, 0, 5, 0, 0, 0, 20, 0, 0, 0, + 224, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, + 8, 0, 0, 0, 32, 0, 0, 0, 0, 128, 11, 0, 0, 0, 0, 0, + 160, 0, 0, 0, 80, 0, 0, 0, 25, 0, 0, 0, 16, 2, 0, 0, + 14, 0, 0, 0, 28, 0, 0, 0, 82, 83, 68, 32, 80, 84, 82, 32, + 89, 66, 79, 67, 72, 83, 32, 0, 220, 24, 254, 7, 0, 0, 0, 0, + 0, 0, 0, 0, 8, 0, 0, 0, + } +) From 558cbf5f17c42e30c081d8a7144196bc863b81ef Mon Sep 17 00:00:00 2001 From: Achilleas Anagnostopoulos Date: Tue, 28 Mar 2017 08:22:46 +0100 Subject: [PATCH 2/5] Implement visitor for examining reported memory map entries Since the actual size of each memory entry is not known in advance (bootloaders may append additional information to it) but needs to be queried off the memory map tag header we cannot reserve space for it as no memory allocation is yet available. Instead, a visitor pattern was implemented to allow the memory manager initialization block to easily mark the appropriate pages as reserved --- kernel/multiboot/multiboot.go | 32 +++++++++++++++++ kernel/multiboot/multiboot_test.go | 56 ++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/kernel/multiboot/multiboot.go b/kernel/multiboot/multiboot.go index 84b84eb..977a40a 100644 --- a/kernel/multiboot/multiboot.go +++ b/kernel/multiboot/multiboot.go @@ -85,6 +85,10 @@ var ( infoData uintptr ) +// MemRegionVisitor defies a visitor function that gets invoked by VisitMemRegions +// for each memory region defined by the boot loader +type MemRegionVisitor func(entry *MemoryMapEntry) + // SetInfoPtr updates the internal multiboot information pointer to the given // value. This function must be invoked before invoking any other function // exported by this package. @@ -92,6 +96,34 @@ func SetInfoPtr(ptr uintptr) { infoData = ptr } +// VisitMemRegions will invoke the supplied visitor for each memory region that +// is defined by the multiboot info data that we received from the bootloader. +func VisitMemRegions(visitor MemRegionVisitor) { + curPtr, size := findTagByType(tagMemoryMap) + if size == 0 { + return + } + + // curPtr points to the memory map header (2 dwords long) + ptrMapHeader := (*mmapHeader)(unsafe.Pointer(curPtr)) + endPtr := curPtr + uintptr(size) + curPtr += 8 + + var entry *MemoryMapEntry + for curPtr != endPtr { + entry = (*MemoryMapEntry)(unsafe.Pointer(curPtr)) + + // Mark unknown entry types as reserved + if entry.Type == 0 || entry.Type > memUnknown { + entry.Type = MemReserved + } + + visitor(entry) + + curPtr += uintptr(ptrMapHeader.entrySize) + } +} + // findTagByType scans the multiboot info data looking for the start of of the // specified type. It returns a pointer to the tag contents start offset and // the content length exluding the tag header. diff --git a/kernel/multiboot/multiboot_test.go b/kernel/multiboot/multiboot_test.go index f97991a..7607e1c 100644 --- a/kernel/multiboot/multiboot_test.go +++ b/kernel/multiboot/multiboot_test.go @@ -40,7 +40,63 @@ func TestFindTagByTypeWithMissingTag(t *testing.T) { } } +func TestVisitMemRegion(t *testing.T) { + specs := []struct { + expPhys uint64 + expLen uint64 + expType MemoryEntryType + }{ + // This region type is actually MemAvailable but we patch it to + // a bogus value to test whether it gets flagged as reserved + {0, 654336, MemReserved}, + {654336, 1024, MemReserved}, + {983040, 65536, MemReserved}, + {1048576, 133038080, MemAvailable}, + {134086656, 131072, MemReserved}, + {4294705152, 262144, MemReserved}, + } + + var visitCount int + + SetInfoPtr(uintptr(unsafe.Pointer(&emptyInfoData[0]))) + VisitMemRegions(func(_ *MemoryMapEntry) { + visitCount++ + }) + + if visitCount != 0 { + t.Fatal("expected visitor not to be invoked when no memory map tag is present") + } + + // Set a bogus type for the first entry in the map + SetInfoPtr(uintptr(unsafe.Pointer(&multibootInfoTestData[0]))) + multibootInfoTestData[128] = 0xFF + + VisitMemRegions(func(entry *MemoryMapEntry) { + if entry.PhysAddress != specs[visitCount].expPhys { + t.Errorf("[visit %d] expected physical address to be %x; got %x", visitCount, specs[visitCount].expPhys, entry.PhysAddress) + } + if entry.Length != specs[visitCount].expLen { + t.Errorf("[visit %d] expected region len to be %x; got %x", visitCount, specs[visitCount].expLen, entry.Length) + } + if entry.Type != specs[visitCount].expType { + t.Errorf("[visit %d] expected region type to be %d; got %d", visitCount, specs[visitCount].expType, entry.Type) + } + visitCount++ + }) + + if visitCount != len(specs) { + t.Errorf("expected the visitor func to be invoked %d times; got %d", len(specs), visitCount) + } +} + var ( + emptyInfoData = []byte{ + 0, 0, 0, 0, // size + 0, 0, 0, 0, // reserved + 0, 0, 0, 0, // tag with type zero and length zero + 0, 0, 0, 0, + } + // A dump of multiboot data when running under qemu. multibootInfoTestData = []byte{ 72, 5, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 9, 0, 0, 0, From 244c8af7529325405da987b4f0fb32ef9181efba Mon Sep 17 00:00:00 2001 From: Achilleas Anagnostopoulos Date: Tue, 28 Mar 2017 22:03:49 +0100 Subject: [PATCH 3/5] Provide method for querying framebuffer info --- kernel/multiboot/multiboot.go | 47 +++++++++++++++++++++++++++++- kernel/multiboot/multiboot_test.go | 27 +++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/kernel/multiboot/multiboot.go b/kernel/multiboot/multiboot.go index 977a40a..a7336fb 100644 --- a/kernel/multiboot/multiboot.go +++ b/kernel/multiboot/multiboot.go @@ -47,6 +47,38 @@ type mmapHeader struct { entryVersion uint32 } +// FramebufferType defines the type of the initialized framebuffer. +type FramebufferType uint8 + +const ( + // FrameBufferTypeIndexed specifies a 256-color palette. + FrameBufferTypeIndexed FramebufferType = iota + + // FramebufferTypeRGB specifies direct RGB mode. + FramebufferTypeRGB + + // FramebufferTypeEGA specifies EGA text mode. + FramebufferTypeEGA +) + +// FramebufferInfo provides information about the initialized framebuffer. +type FramebufferInfo struct { + // The framebuffer physical address. + PhysAddr uint64 + + // Row pitch in bytes. + Pitch uint32 + + // Width and height in pixels (or characters if Type = FramebufferTypeEGA) + Width, Height uint32 + + // Bits per pixel (non EGA modes only). + Bpp uint8 + + // Framebuffer type. + Type FramebufferType +} + // MemoryEntryType defines the type of a MemoryMapEntry. type MemoryEntryType uint32 @@ -86,7 +118,7 @@ var ( ) // MemRegionVisitor defies a visitor function that gets invoked by VisitMemRegions -// for each memory region defined by the boot loader +// for each memory region provided by the boot loader. type MemRegionVisitor func(entry *MemoryMapEntry) // SetInfoPtr updates the internal multiboot information pointer to the given @@ -124,6 +156,19 @@ func VisitMemRegions(visitor MemRegionVisitor) { } } +// GetFramebufferInfo returns information about the framebuffer initialized by the +// bootloader. This function returns nil if no framebuffer info is available. +func GetFramebufferInfo() *FramebufferInfo { + var info *FramebufferInfo + + curPtr, size := findTagByType(tagFramebufferInfo) + if size != 0 { + info = (*FramebufferInfo)(unsafe.Pointer(curPtr)) + } + + return info +} + // findTagByType scans the multiboot info data looking for the start of of the // specified type. It returns a pointer to the tag contents start offset and // the content length exluding the tag header. diff --git a/kernel/multiboot/multiboot_test.go b/kernel/multiboot/multiboot_test.go index 7607e1c..62193b3 100644 --- a/kernel/multiboot/multiboot_test.go +++ b/kernel/multiboot/multiboot_test.go @@ -89,6 +89,33 @@ func TestVisitMemRegion(t *testing.T) { } } +func TestGetFramebufferInfo(t *testing.T) { + SetInfoPtr(uintptr(unsafe.Pointer(&emptyInfoData[0]))) + + if GetFramebufferInfo() != nil { + t.Fatalf("expected GetFramebufferInfo() to return nil when no framebuffer tag is present") + } + + SetInfoPtr(uintptr(unsafe.Pointer(&multibootInfoTestData[0]))) + fbInfo := GetFramebufferInfo() + + if fbInfo.Type != FramebufferTypeEGA { + t.Errorf("expected framebuffer type to be %d; got %d", FramebufferTypeEGA, fbInfo.Type) + } + + if fbInfo.PhysAddr != 0xB8000 { + t.Errorf("expected physical address for EGA text mode to be 0xB8000; got %x", fbInfo.PhysAddr) + } + + if fbInfo.Width != 80 || fbInfo.Height != 25 { + t.Errorf("expected framebuffer dimensions to be 80x25; got %dx%d", fbInfo.Width, fbInfo.Height) + } + + if fbInfo.Pitch != 160 { + t.Errorf("expected pitch to be 160; got %x", fbInfo.Pitch) + } +} + var ( emptyInfoData = []byte{ 0, 0, 0, 0, // size From c15f27235c09a6c7238d63aa2cb53014146b76ac Mon Sep 17 00:00:00 2001 From: Achilleas Anagnostopoulos Date: Wed, 29 Mar 2017 07:50:29 +0100 Subject: [PATCH 4/5] Update rt0 code to check for multiboot support and call kernel.Kmain We still keep the required main func in stub.go to prevent the compiler from optimizing the code out. We also force the compiler not to inline the call to kernel.Kmain so we can find the symbol in the generated .o file. --- arch/x86/asm/rt0.s | 59 ++++++++++++++++++++++++++++++++++++++++++++-- boot.go | 18 -------------- kernel/kmain.go | 21 +++++++++++++++-- stub.go | 15 ++++++++++++ 4 files changed, 91 insertions(+), 22 deletions(-) delete mode 100644 boot.go create mode 100644 stub.go diff --git a/arch/x86/asm/rt0.s b/arch/x86/asm/rt0.s index 95ea421..8519c49 100644 --- a/arch/x86/asm/rt0.s +++ b/arch/x86/asm/rt0.s @@ -24,17 +24,29 @@ section .text bits 32 align 4 +MULTIBOOT_MAGIC equ 0x36d76289 + +err_unsupported_bootloader db '[rt0] kernel not loaded by multiboot-compliant bootloader', 0 +err_kmain_returned db '[rt0] kMain returned; halting system', 0 + ;------------------------------------------------------------------------------ ; Kernel arch-specific entry point ; ; The boot loader will jump to this symbol after setting up the CPU according ; to the multiboot standard. At this point: +; - A20 is enabled ; - The CPU is using 32-bit protected mode ; - Interrupts are disabled ; - Paging is disabled +; - EAX contains the magic value ‘0x36d76289’; the presence of this value indicates +; to the operating system that it was loaded by a Multiboot-compliant boot loader +; - EBX contains the 32-bit physical address of the Multiboot information structure ;------------------------------------------------------------------------------ global _rt0_entry _rt0_entry: + cmp eax, MULTIBOOT_MAGIC + jne unsupported_bootloader + ; Initalize our stack by pointing ESP to the BSS-allocated stack. In x86, ; stack grows downwards so we need to point ESP to stack_top mov esp, stack_top @@ -48,19 +60,60 @@ _rt0_entry: mov dword [g0_stack_lo], stack_bottom mov dword [g0_stackguard0], stack_bottom - extern main.main - call main.main + ; push multiboot info ptr to the stack and call the kernel entrypoint + push ebx + extern kernel.Kmain + call kernel.Kmain + + ; kmain should never return + mov edi, err_kmain_returned + call write_string ; Main should never return; halt the CPU +halt: cli hlt + +unsupported_bootloader: + mov edi, err_unsupported_bootloader + call write_string + jmp halt .end: +;------------------------------------------------------------------------------ +; Write the NULL-terminated string contained in edi to the screen using white +; text on red background. Assumes that text-mode is enabled and that its +; physical address is 0xb8000. +;------------------------------------------------------------------------------ +write_string: + push eax + push ebx + + mov ebx,0xb8000 + mov ah, 0x4F +next_char: + mov al, byte[edi] + test al, al + jz done + + mov word [ebx], ax + add ebx, 2 + inc edi + jmp next_char + +done: + pop ebx + pop eax + ret + ;------------------------------------------------------------------------------ ; Load GDT and flush CPU caches ;------------------------------------------------------------------------------ _rt0_load_gdt: + push eax + push ebx + ; Go code uses the GS register to access the TLS. Set the base address ; for the GS descriptor to point to our tls0 table mov eax, tls0 @@ -84,6 +137,8 @@ update_descriptors: mov ax, GS_SEG mov gs, ax + pop ebx + pop eax ret ;------------------------------------------------------------------------------ diff --git a/boot.go b/boot.go deleted file mode 100644 index fb85268..0000000 --- a/boot.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -import "github.com/achilleasa/gopher-os/kernel" - -// main is the only Go symbol that is visible (exported) from the rt0 initialization -// code. This function works as a trampoline for calling the actual kernel entrypoint -// (kernel.Kmain) and its intentionally defined to prevent the Go compiler from -// optimizing away the actual kernel code as its not aware of the presence of the -// rt0 code. -// -// The main function is invoked by the rt0 assembly code after setting up the GDT -// and setting up a a minimal g0 struct that allows Go code using the 4K stack -// allocated by the assembly code. -// -// main is not expected to return. If it does, the rt0 code will halt the CPU. -func main() { - kernel.Kmain() -} diff --git a/kernel/kmain.go b/kernel/kmain.go index 85c1df0..1943eff 100644 --- a/kernel/kmain.go +++ b/kernel/kmain.go @@ -1,5 +1,22 @@ package kernel -// Kmain is invoked by the boot.go and implements the actual kernel entrypoint. -func Kmain() { +import ( + _ "unsafe" // required for go:linkname + + "github.com/achilleasa/gopher-os/kernel/multiboot" +) + +// Kmain is the only Go symbol that is visible (exported) from the rt0 initialization +// code. This function is invoked by the rt0 assembly code after setting up the GDT +// and setting up a a minimal g0 struct that allows Go code using the 4K stack +// allocated by the assembly code. +// +// The rt0 code passes the address of the multiboot info payload provided by the +// bootloader. +// +// Kmain is not expected to return. If it does, the rt0 code will halt the CPU. +// +//go:noinline +func Kmain(multibootInfoPtr uint32) { + multiboot.SetInfoPtr(uintptr(multibootInfoPtr)) } diff --git a/stub.go b/stub.go new file mode 100644 index 0000000..80ae494 --- /dev/null +++ b/stub.go @@ -0,0 +1,15 @@ +package main + +import "github.com/achilleasa/gopher-os/kernel" + +var multibootInfoPtr uint32 + +// main makes a dummy call to the actual kernel main entrypoint function. It +// is intentionally defined to prevent the Go compiler from optimizing away the +// real kernel code. +// +// A global variable is passed as an argument to Kmain to prevent the compiler +// from inlining the actual call and removing Kmain from the generated .o file. +func main() { + kernel.Kmain(multibootInfoPtr) +} From 6dd00b5934d8b8bda76e5990145de2397140b6aa Mon Sep 17 00:00:00 2001 From: Achilleas Anagnostopoulos Date: Wed, 29 Mar 2017 07:54:08 +0100 Subject: [PATCH 5/5] Create a global symbol alias for kernel.Kmain The go compiler exposes a fully qualified symbol for kernel.Kmain that also includes the full package name (github.com/.../kernel.Kmain). Since nasm cannot work with external symbols that include slashes we use objcopy to create an alias symbol "kernel.Kmain" that points to the symbol generated by the go compiler. To use the "--add-symbol" argument we need to use objcopy 2.6+. The makefile was modified to include an additional pre-compile check for the installed objcopy version. --- Makefile | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 57b8ce6..8fd2d6d 100644 --- a/Makefile +++ b/Makefile @@ -18,12 +18,15 @@ GOARCH := 386 LD_FLAGS := -n -melf_i386 -T arch/$(ARCH)/script/linker.ld -static --no-ld-generated-unwind-info AS_FLAGS := -g -f elf32 -F dwarf -I arch/$(ARCH)/asm/ +MIN_OBJCOPY_VERSION := 2.26.0 +HAVE_VALID_OBJCOPY := $(shell objcopy -V | head -1 | awk -F ' ' '{print "$(MIN_OBJCOPY_VERSION)\n" $$NF}' | sort -ct. -k1,1n -k2,2n && echo "y") + asm_src_files := $(wildcard arch/$(ARCH)/asm/*.s) asm_obj_files := $(patsubst arch/$(ARCH)/asm/%.s, $(BUILD_DIR)/arch/$(ARCH)/asm/%.o, $(asm_src_files)) -.PHONY: kernel iso clean +.PHONY: kernel iso clean binutils_version_check -kernel: $(kernel_target) +kernel: binutils_version_check $(kernel_target) $(kernel_target): $(asm_obj_files) go.o @echo "[$(LD)] linking kernel-$(ARCH).bin" @@ -45,9 +48,16 @@ go.o: @# build/go.o is a elf32 object file but all go symbols are unexported. Our @# asm entrypoint code needs to know the address to 'main.main' so we use - @# objcopy to make that symbol exportable - @echo "[objcopy] export 'main.main' symbol in go.o" - @objcopy --globalize-symbol='main.main' $(BUILD_DIR)/go.o $(BUILD_DIR)/go.o + @# objcopy to make that symbol exportable. Since nasm does not support externs + @# with slashes we create a global symbol alias for kernel.Kmain + @echo "[objcopy] creating global symbol alias 'kernel.Kmain' for 'github.com/achilleasa/gopher-os/kernel.Kmain' in go.o" + @objcopy \ + --add-symbol kernel.Kmain=.text:0x`nm $(BUILD_DIR)/go.o | grep "kernel.Kmain" | cut -d' ' -f1` \ + $(BUILD_DIR)/go.o $(BUILD_DIR)/go.o + +binutils_version_check: + @echo "[binutils] checking that installed objcopy version is >= $(MIN_OBJCOPY_VERSION)" + @if [ "$(HAVE_VALID_OBJCOPY)" != "y" ]; then echo "[binutils] error: a more up to date binutils installation is required" ; exit 1 ; fi $(BUILD_DIR)/arch/$(ARCH)/asm/%.o: arch/$(ARCH)/asm/%.s @mkdir -p $(shell dirname $@)