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 }