github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/src/runtime/runtime_nintendoswitch.go (about)

     1  //go:build nintendoswitch
     2  
     3  package runtime
     4  
     5  import "unsafe"
     6  
     7  type timeUnit int64
     8  
     9  const (
    10  	// Handles
    11  	infoTypeTotalMemorySize = 6          // Total amount of memory available for process.
    12  	infoTypeUsedMemorySize  = 7          // Amount of memory currently used by process.
    13  	currentProcessHandle    = 0xFFFF8001 // Pseudo handle for the current process.
    14  
    15  	// Types of config Entry
    16  	envEntryTypeEndOfList        = 0 // Entry list terminator.
    17  	envEntryTypeMainThreadHandle = 1 // Provides the handle to the main thread.
    18  	envEntryTypeOverrideHeap     = 3 // Provides heap override information.
    19  
    20  	// Default heap size allocated by libnx
    21  	defaultHeapSize = 0x2000000 * 16
    22  
    23  	debugInit = false
    24  )
    25  
    26  //go:extern _saved_return_address
    27  var savedReturnAddress uintptr
    28  
    29  //export __stack_top
    30  var stackTop uintptr
    31  
    32  //go:extern _context
    33  var context uintptr
    34  
    35  //go:extern _main_thread
    36  var mainThread uintptr
    37  
    38  var (
    39  	heapStart = uintptr(0)
    40  	heapEnd   = uintptr(0)
    41  	usedRam   = uint64(0)
    42  	totalRam  = uint64(0)
    43  	totalHeap = uint64(0)
    44  )
    45  
    46  func preinit() {
    47  	// Unsafe to use heap here
    48  	setupEnv()
    49  	setupHeap()
    50  }
    51  
    52  // Entry point for Go. Initialize all packages and call main.main().
    53  //
    54  //export main
    55  func main() {
    56  	preinit()
    57  	run()
    58  
    59  	// Call exit to correctly finish the program
    60  	// Without this, the application crashes at start, not sure why
    61  	for {
    62  		exit(0)
    63  	}
    64  }
    65  
    66  // sleepTicks sleeps for the specified system ticks
    67  func sleepTicks(d timeUnit) {
    68  	svcSleepThread(uint64(ticksToNanoseconds(d)))
    69  }
    70  
    71  // armTicksToNs converts cpu ticks to nanoseconds
    72  // Nintendo Switch CPU ticks has a fixed rate at 19200000
    73  // It is basically 52 ns per tick
    74  // The formula 625 / 12 is equivalent to 1e9 / 19200000
    75  func ticksToNanoseconds(tick timeUnit) int64 {
    76  	return int64(tick * 625 / 12)
    77  }
    78  
    79  func nanosecondsToTicks(ns int64) timeUnit {
    80  	return timeUnit(12 * ns / 625)
    81  }
    82  
    83  func ticks() timeUnit {
    84  	return timeUnit(ticksToNanoseconds(timeUnit(getArmSystemTick())))
    85  }
    86  
    87  var stdoutBuffer = make([]byte, 120)
    88  var position = 0
    89  
    90  func putchar(c byte) {
    91  	if c == '\n' || position >= len(stdoutBuffer) {
    92  		svcOutputDebugString(&stdoutBuffer[0], uint64(position))
    93  		position = 0
    94  		return
    95  	}
    96  
    97  	stdoutBuffer[position] = c
    98  	position++
    99  }
   100  
   101  func abort() {
   102  	for {
   103  		exit(1)
   104  	}
   105  }
   106  
   107  //export write
   108  func libc_write(fd int32, buf *byte, count int) int {
   109  	// TODO: Proper handling write
   110  	for i := 0; i < count; i++ {
   111  		putchar(*buf)
   112  		buf = (*byte)(unsafe.Add(unsafe.Pointer(buf), 1))
   113  	}
   114  	return count
   115  }
   116  
   117  // exit checks if a savedReturnAddress were provided by the launcher
   118  // if so, calls the nxExit which restores the stack and returns to launcher
   119  // otherwise just calls systemcall exit
   120  func exit(code int) {
   121  	if savedReturnAddress == 0 {
   122  		svcExitProcess(code)
   123  		return
   124  	}
   125  
   126  	nxExit(code, stackTop, savedReturnAddress)
   127  }
   128  
   129  type configEntry struct {
   130  	Key   uint32
   131  	Flags uint32
   132  	Value [2]uint64
   133  }
   134  
   135  func setupEnv() {
   136  	if debugInit {
   137  		println("Saved Return Address:", savedReturnAddress)
   138  		println("Context:", context)
   139  		println("Main Thread Handle:", mainThread)
   140  	}
   141  
   142  	// See https://switchbrew.org/w/index.php?title=Homebrew_ABI
   143  	// Here we parse only the required configs for initializing
   144  	if context != 0 {
   145  		ptr := context
   146  		entry := (*configEntry)(unsafe.Pointer(ptr))
   147  		for entry.Key != envEntryTypeEndOfList {
   148  			switch entry.Key {
   149  			case envEntryTypeOverrideHeap:
   150  				if debugInit {
   151  					println("Got heap override")
   152  				}
   153  				heapStart = uintptr(entry.Value[0])
   154  				heapEnd = heapStart + uintptr(entry.Value[1])
   155  			case envEntryTypeMainThreadHandle:
   156  				mainThread = uintptr(entry.Value[0])
   157  			default:
   158  				if entry.Flags&1 > 0 {
   159  					// Mandatory but not parsed
   160  					runtimePanic("mandatory config entry not parsed")
   161  				}
   162  			}
   163  			ptr += unsafe.Sizeof(configEntry{})
   164  			entry = (*configEntry)(unsafe.Pointer(ptr))
   165  		}
   166  	}
   167  	// Fetch used / total RAM for allocating HEAP
   168  	svcGetInfo(&totalRam, infoTypeTotalMemorySize, currentProcessHandle, 0)
   169  	svcGetInfo(&usedRam, infoTypeUsedMemorySize, currentProcessHandle, 0)
   170  }
   171  
   172  func setupHeap() {
   173  	if heapStart != 0 {
   174  		if debugInit {
   175  			print("Heap already overrided by hblauncher")
   176  		}
   177  		// Already overrided
   178  		return
   179  	}
   180  
   181  	if debugInit {
   182  		print("No heap override. Using normal initialization")
   183  	}
   184  
   185  	size := uint32(defaultHeapSize)
   186  
   187  	if totalRam > usedRam+0x200000 {
   188  		// Get maximum possible heap
   189  		size = uint32(totalRam-usedRam-0x200000) & ^uint32(0x1FFFFF)
   190  	}
   191  
   192  	if size < defaultHeapSize {
   193  		size = defaultHeapSize
   194  	}
   195  
   196  	if debugInit {
   197  		println("Trying to allocate", size, "bytes of heap")
   198  	}
   199  
   200  	svcSetHeapSize(&heapStart, uint64(size))
   201  
   202  	if heapStart == 0 {
   203  		runtimePanic("failed to allocate heap")
   204  	}
   205  
   206  	totalHeap = uint64(size)
   207  
   208  	heapEnd = heapStart + uintptr(size)
   209  
   210  	if debugInit {
   211  		println("Heap Start", heapStart)
   212  		println("Heap End  ", heapEnd)
   213  		println("Total Heap", totalHeap)
   214  	}
   215  }
   216  
   217  // growHeap tries to grow the heap size. It returns true if it succeeds, false
   218  // otherwise.
   219  func growHeap() bool {
   220  	// Growing the heap is unimplemented.
   221  	return false
   222  }
   223  
   224  // getHeapBase returns the start address of the heap
   225  // this is externally linked by gonx
   226  func getHeapBase() uintptr {
   227  	return heapStart
   228  }
   229  
   230  // getHeapEnd returns the end address of the heap
   231  // this is externally linked by gonx
   232  func getHeapEnd() uintptr {
   233  	return heapEnd
   234  }
   235  
   236  //go:extern __data_start
   237  var dataStartSymbol [0]byte
   238  
   239  //go:extern __data_end
   240  var dataEndSymbol [0]byte
   241  
   242  //go:extern __bss_start
   243  var bssStartSymbol [0]byte
   244  
   245  //go:extern __bss_end
   246  var bssEndSymbol [0]byte
   247  
   248  // Find global variables.
   249  // The linker script provides __*_start and __*_end symbols that can be used to
   250  // scan the given sections. They are already aligned so don't need to be
   251  // manually aligned here.
   252  func findGlobals(found func(start, end uintptr)) {
   253  	dataStart := uintptr(unsafe.Pointer(&dataStartSymbol))
   254  	dataEnd := uintptr(unsafe.Pointer(&dataEndSymbol))
   255  	found(dataStart, dataEnd)
   256  	bssStart := uintptr(unsafe.Pointer(&bssStartSymbol))
   257  	bssEnd := uintptr(unsafe.Pointer(&bssEndSymbol))
   258  	found(bssStart, bssEnd)
   259  }
   260  
   261  // getContextPtr returns the hblauncher context
   262  // this is externally linked by gonx
   263  func getContextPtr() uintptr {
   264  	return context
   265  }
   266  
   267  // getMainThreadHandle returns the main thread handler if any
   268  // this is externally linked by gonx
   269  func getMainThreadHandle() uintptr {
   270  	return mainThread
   271  }
   272  
   273  //export armGetSystemTick
   274  func getArmSystemTick() int64
   275  
   276  // nxExit exits the program to homebrew launcher
   277  //
   278  //export __nx_exit
   279  func nxExit(code int, stackTop uintptr, exitFunction uintptr)
   280  
   281  // Horizon System Calls
   282  // svcSetHeapSize Set the process heap to a given size. It can both extend and shrink the heap.
   283  // svc 0x01
   284  //
   285  //export svcSetHeapSize
   286  func svcSetHeapSize(addr *uintptr, length uint64) uint64
   287  
   288  // svcExitProcess Exits the current process.
   289  // svc 0x07
   290  //
   291  //export svcExitProcess
   292  func svcExitProcess(code int)
   293  
   294  // svcSleepThread Sleeps the current thread for the specified amount of time.
   295  // svc 0x0B
   296  //
   297  //export svcSleepThread
   298  func svcSleepThread(nanos uint64)
   299  
   300  // svcOutputDebugString Outputs debug text, if used during debugging.
   301  // svc 0x27
   302  //
   303  //export svcOutputDebugString
   304  func svcOutputDebugString(str *uint8, size uint64) uint64
   305  
   306  // svcGetInfo Retrieves information about the system, or a certain kernel object.
   307  // svc 0x29
   308  //
   309  //export svcGetInfo
   310  func svcGetInfo(output *uint64, id0 uint32, handle uint32, id1 uint64) uint64
   311  
   312  func hardwareRand() (n uint64, ok bool) {
   313  	// TODO: see whether there is a RNG and use it.
   314  	return 0, false
   315  }