
In the early days of TinyGo, the idea of `postinit` was to enable interrupts only after initializers have run. Which kind of makes sense... except that `time.Sleep` is allowed in init code and `time.Sleep` requires interrupts to be enabled. Therefore, interrupts must be enabled while initializers are being run. This commit simply moves the enabling of interrupts to a point right before running package initializers. It also removes `runtime.postinit`, which is not necessary anymore (and was only used on AVR).
278 строки
6,4 КиБ
Go
278 строки
6,4 КиБ
Go
// +build nintendoswitch
|
|
|
|
package runtime
|
|
|
|
import "unsafe"
|
|
|
|
type timeUnit int64
|
|
|
|
const (
|
|
// Handles
|
|
infoTypeTotalMemorySize = 6 // Total amount of memory available for process.
|
|
infoTypeUsedMemorySize = 7 // Amount of memory currently used by process.
|
|
currentProcessHandle = 0xFFFF8001 // Pseudo handle for the current process.
|
|
|
|
// Types of config Entry
|
|
envEntryTypeEndOfList = 0 // Entry list terminator.
|
|
envEntryTypeMainThreadHandle = 1 // Provides the handle to the main thread.
|
|
envEntryTypeOverrideHeap = 3 // Provides heap override information.
|
|
|
|
// Default heap size allocated by libnx
|
|
defaultHeapSize = 0x2000000 * 16
|
|
|
|
debugInit = false
|
|
)
|
|
|
|
//go:extern _saved_return_address
|
|
var savedReturnAddress uintptr
|
|
|
|
//export __stack_top
|
|
var stackTop uintptr
|
|
|
|
//go:extern _context
|
|
var context uintptr
|
|
|
|
//go:extern _main_thread
|
|
var mainThread uintptr
|
|
|
|
var (
|
|
heapStart = uintptr(0)
|
|
heapEnd = uintptr(0)
|
|
usedRam = uint64(0)
|
|
totalRam = uint64(0)
|
|
totalHeap = uint64(0)
|
|
)
|
|
|
|
func preinit() {
|
|
// Unsafe to use heap here
|
|
setupEnv()
|
|
setupHeap()
|
|
}
|
|
|
|
// Entry point for Go. Initialize all packages and call main.main().
|
|
//export main
|
|
func main() {
|
|
preinit()
|
|
run()
|
|
|
|
// Call exit to correctly finish the program
|
|
// Without this, the application crashes at start, not sure why
|
|
for {
|
|
exit(0)
|
|
}
|
|
}
|
|
|
|
// sleepTicks sleeps for the specified system ticks
|
|
func sleepTicks(d timeUnit) {
|
|
svcSleepThread(uint64(ticksToNanoseconds(d)))
|
|
}
|
|
|
|
// armTicksToNs converts cpu ticks to nanoseconds
|
|
// Nintendo Switch CPU ticks has a fixed rate at 19200000
|
|
// It is basically 52 ns per tick
|
|
// The formula 625 / 12 is equivalent to 1e9 / 19200000
|
|
func ticksToNanoseconds(tick timeUnit) int64 {
|
|
return int64(tick * 625 / 12)
|
|
}
|
|
|
|
func nanosecondsToTicks(ns int64) timeUnit {
|
|
return timeUnit(12 * ns / 625)
|
|
}
|
|
|
|
func ticks() timeUnit {
|
|
return timeUnit(ticksToNanoseconds(timeUnit(getArmSystemTick())))
|
|
}
|
|
|
|
var stdoutBuffer = make([]byte, 120)
|
|
var position = 0
|
|
|
|
func putchar(c byte) {
|
|
if c == '\n' || position >= len(stdoutBuffer) {
|
|
svcOutputDebugString(&stdoutBuffer[0], uint64(position))
|
|
position = 0
|
|
return
|
|
}
|
|
|
|
stdoutBuffer[position] = c
|
|
position++
|
|
}
|
|
|
|
func abort() {
|
|
for {
|
|
exit(1)
|
|
}
|
|
}
|
|
|
|
//export write
|
|
func write(fd int32, buf *byte, count int) int {
|
|
// TODO: Proper handling write
|
|
for i := 0; i < count; i++ {
|
|
putchar(*buf)
|
|
buf = (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(buf)) + 1))
|
|
}
|
|
return count
|
|
}
|
|
|
|
// exit checks if a savedReturnAddress were provided by the launcher
|
|
// if so, calls the nxExit which restores the stack and returns to launcher
|
|
// otherwise just calls systemcall exit
|
|
func exit(code int) {
|
|
if savedReturnAddress == 0 {
|
|
svcExitProcess(code)
|
|
return
|
|
}
|
|
|
|
nxExit(code, stackTop, savedReturnAddress)
|
|
}
|
|
|
|
type configEntry struct {
|
|
Key uint32
|
|
Flags uint32
|
|
Value [2]uint64
|
|
}
|
|
|
|
func setupEnv() {
|
|
if debugInit {
|
|
println("Saved Return Address:", savedReturnAddress)
|
|
println("Context:", context)
|
|
println("Main Thread Handle:", mainThread)
|
|
}
|
|
|
|
// See https://switchbrew.org/w/index.php?title=Homebrew_ABI
|
|
// Here we parse only the required configs for initializing
|
|
if context != 0 {
|
|
ptr := context
|
|
entry := (*configEntry)(unsafe.Pointer(ptr))
|
|
for entry.Key != envEntryTypeEndOfList {
|
|
switch entry.Key {
|
|
case envEntryTypeOverrideHeap:
|
|
if debugInit {
|
|
println("Got heap override")
|
|
}
|
|
heapStart = uintptr(entry.Value[0])
|
|
heapEnd = heapStart + uintptr(entry.Value[1])
|
|
case envEntryTypeMainThreadHandle:
|
|
mainThread = uintptr(entry.Value[0])
|
|
default:
|
|
if entry.Flags&1 > 0 {
|
|
// Mandatory but not parsed
|
|
runtimePanic("mandatory config entry not parsed")
|
|
}
|
|
}
|
|
ptr += unsafe.Sizeof(configEntry{})
|
|
entry = (*configEntry)(unsafe.Pointer(ptr))
|
|
}
|
|
}
|
|
// Fetch used / total RAM for allocating HEAP
|
|
svcGetInfo(&totalRam, infoTypeTotalMemorySize, currentProcessHandle, 0)
|
|
svcGetInfo(&usedRam, infoTypeUsedMemorySize, currentProcessHandle, 0)
|
|
}
|
|
|
|
func setupHeap() {
|
|
if heapStart != 0 {
|
|
if debugInit {
|
|
print("Heap already overrided by hblauncher")
|
|
}
|
|
// Already overrided
|
|
return
|
|
}
|
|
|
|
if debugInit {
|
|
print("No heap override. Using normal initialization")
|
|
}
|
|
|
|
size := uint32(defaultHeapSize)
|
|
|
|
if totalRam > usedRam+0x200000 {
|
|
// Get maximum possible heap
|
|
size = uint32(totalRam-usedRam-0x200000) & ^uint32(0x1FFFFF)
|
|
}
|
|
|
|
if size < defaultHeapSize {
|
|
size = defaultHeapSize
|
|
}
|
|
|
|
if debugInit {
|
|
println("Trying to allocate", size, "bytes of heap")
|
|
}
|
|
|
|
svcSetHeapSize(&heapStart, uint64(size))
|
|
|
|
if heapStart == 0 {
|
|
runtimePanic("failed to allocate heap")
|
|
}
|
|
|
|
totalHeap = uint64(size)
|
|
|
|
heapEnd = heapStart + uintptr(size)
|
|
|
|
if debugInit {
|
|
println("Heap Start", heapStart)
|
|
println("Heap End ", heapEnd)
|
|
println("Total Heap", totalHeap)
|
|
}
|
|
}
|
|
|
|
// growHeap tries to grow the heap size. It returns true if it succeeds, false
|
|
// otherwise.
|
|
func growHeap() bool {
|
|
// Growing the heap is unimplemented.
|
|
return false
|
|
}
|
|
|
|
// getHeapBase returns the start address of the heap
|
|
// this is externally linked by gonx
|
|
func getHeapBase() uintptr {
|
|
return heapStart
|
|
}
|
|
|
|
// getHeapEnd returns the end address of the heap
|
|
// this is externally linked by gonx
|
|
func getHeapEnd() uintptr {
|
|
return heapEnd
|
|
}
|
|
|
|
// getContextPtr returns the hblauncher context
|
|
// this is externally linked by gonx
|
|
func getContextPtr() uintptr {
|
|
return context
|
|
}
|
|
|
|
// getMainThreadHandle returns the main thread handler if any
|
|
// this is externally linked by gonx
|
|
func getMainThreadHandle() uintptr {
|
|
return mainThread
|
|
}
|
|
|
|
//export armGetSystemTick
|
|
func getArmSystemTick() int64
|
|
|
|
// nxExit exits the program to homebrew launcher
|
|
//export __nx_exit
|
|
func nxExit(code int, stackTop uintptr, exitFunction uintptr)
|
|
|
|
// Horizon System Calls
|
|
// svcSetHeapSize Set the process heap to a given size. It can both extend and shrink the heap.
|
|
// svc 0x01
|
|
//export svcSetHeapSize
|
|
func svcSetHeapSize(addr *uintptr, length uint64) uint64
|
|
|
|
// svcExitProcess Exits the current process.
|
|
// svc 0x07
|
|
//export svcExitProcess
|
|
func svcExitProcess(code int)
|
|
|
|
// svcSleepThread Sleeps the current thread for the specified amount of time.
|
|
// svc 0x0B
|
|
//export svcSleepThread
|
|
func svcSleepThread(nanos uint64)
|
|
|
|
// svcOutputDebugString Outputs debug text, if used during debugging.
|
|
// svc 0x27
|
|
//export svcOutputDebugString
|
|
func svcOutputDebugString(str *uint8, size uint64) uint64
|
|
|
|
// svcGetInfo Retrieves information about the system, or a certain kernel object.
|
|
// svc 0x29
|
|
//export svcGetInfo
|
|
func svcGetInfo(output *uint64, id0 uint32, handle uint32, id1 uint64) uint64
|