Add ESP32-S3 support (#5091)

* feat: add initial support for ESP32-S3 (#3442)

* feat: add initial support for esp32-s3

* esp32s3: fix merge errors

* esp32s3: Fix Watchdog registers bad names

* esp32s3: fix linker relocation errors and support for ESP binary

* esp32s3: fix memory section overlap

* esp32s3: correct clock frequencies

* esp32s3: more stable cpu

* esp32s3: enable basic gpio support

* esp32s3: simplify loading and check extensions

* esp32s3: synchronize cpu features with clang

* esp32s3: correct iram origin

---------

Co-authored-by: Denys Vitali <denys@denv.it>
Co-authored-by: Olivier Fauchon <ofauchon2204@gmail.com>
This commit is contained in:
cjpeterson
2025-11-24 04:11:47 -07:00
committed by GitHub
parent 97a2cb4a37
commit fbbaa5e580
9 changed files with 714 additions and 3 deletions
+1 -1
View File
@@ -1042,7 +1042,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if err != nil {
return result, err
}
case "esp32", "esp32-img", "esp32c3", "esp8266":
case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp8266":
// Special format for the ESP family of chips (parsed by the ROM
// bootloader).
result.Binary = filepath.Join(tmpdir, "main"+outext)
+1
View File
@@ -28,6 +28,7 @@ func TestClangAttributes(t *testing.T) {
"cortex-m4",
"cortex-m7",
"esp32c3",
"esp32s3",
"fe310",
"gameboy-advance",
"k210",
+2 -1
View File
@@ -100,11 +100,12 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
chip_id := map[string]uint16{
"esp32": 0x0000,
"esp32c3": 0x0005,
"esp32s3": 0x0009,
}[chip]
// Image header.
switch chip {
case "esp32", "esp32c3":
case "esp32", "esp32c3", "esp32s3":
// Header format:
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71
// Note: not adding a SHA256 hash as the binary is modified by
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build scheduler.tasks && esp32
//go:build scheduler.tasks && (esp32 || esp32s3)
package task
+312
View File
@@ -0,0 +1,312 @@
//go:build esp32s3
package machine
import (
"device/esp"
"errors"
"runtime/volatile"
"unsafe"
)
const deviceName = esp.Device
const xtalClock = 40_000000 // 40MHz
const apbClock = 80_000000 // 80MHz
const cryptoPWMClock = 160_000000 // 160MHz
// GetCPUFrequency returns the current CPU frequency of the chip.
func GetCPUFrequency() (uint32, error) {
switch esp.SYSTEM.GetSYSCLK_CONF_SOC_CLK_SEL() {
case 0:
return xtalClock / (esp.SYSTEM.GetSYSCLK_CONF_PRE_DIV_CNT() + 1), nil
case 1:
switch esp.SYSTEM.GetCPU_PER_CONF_CPUPERIOD_SEL() {
case 0:
return 80e6, nil
case 1:
return 160e6, nil
case 2:
// If esp.SYSTEM.GetCPU_PER_CONF_PLL_FREQ_SEL() == 1, this is undefined
return 240e6, nil
}
case 2:
//RC Fast Clock
return (175e5) / (esp.SYSTEM.GetSYSCLK_CONF_PRE_DIV_CNT() + 1), nil
}
return 0, errors.New("machine: Unable to determine current cpu frequency")
}
// SetCPUFrequency sets the frequency of the CPU to one of several targets
func SetCPUFrequency(frequency uint32) error {
// Always assume we are on PLL. Lower frequencies can be set with a different
// clock source, but this will change the behavior of APB clock and Crypto PWM
// clock
//esp.SYSTEM.SetSYSCLK_CONF_SOC_CLK_SEL(1)
switch frequency {
case 80_000000:
esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(0)
esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(0) // Reduce PLL freq when possible
return nil
case 160_000000:
esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(1)
esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(0)
return nil
case 240_000000:
esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(1) // Increase PLL freq when needed
esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(2)
return nil
}
return errors.New("machine: Unsupported CPU frequency selected. Supported: 80, 160, 240 MHz")
}
var (
ErrInvalidSPIBus = errors.New("machine: invalid SPI bus")
)
const (
PinOutput PinMode = iota
PinInput
PinInputPullup
PinInputPulldown
)
// Hardware pin numbers
const (
GPIO0 Pin = 0
GPIO1 Pin = 1
GPIO2 Pin = 2
GPIO3 Pin = 3
GPIO4 Pin = 4
GPIO5 Pin = 5
GPIO6 Pin = 6
GPIO7 Pin = 7
GPIO8 Pin = 8
GPIO9 Pin = 9
GPIO10 Pin = 10
GPIO11 Pin = 11
GPIO12 Pin = 12
GPIO13 Pin = 13
GPIO14 Pin = 14
GPIO15 Pin = 15
GPIO16 Pin = 16
GPIO17 Pin = 17
GPIO18 Pin = 18
GPIO19 Pin = 19
GPIO20 Pin = 20
GPIO21 Pin = 21
GPIO26 Pin = 26
GPIO27 Pin = 27
GPIO28 Pin = 28
GPIO29 Pin = 29
GPIO30 Pin = 30
GPIO31 Pin = 31
GPIO32 Pin = 32
GPIO33 Pin = 33
GPIO34 Pin = 34
GPIO35 Pin = 35
GPIO36 Pin = 36
GPIO37 Pin = 37
GPIO38 Pin = 38
GPIO39 Pin = 39
GPIO40 Pin = 40
GPIO41 Pin = 41
GPIO42 Pin = 42
GPIO43 Pin = 43
GPIO44 Pin = 44
GPIO45 Pin = 45
GPIO46 Pin = 46
GPIO47 Pin = 47
GPIO48 Pin = 48
)
// Configure this pin with the given configuration.
func (p Pin) Configure(config PinConfig) {
// Output function 256 is a special value reserved for use as a regular GPIO
// pin. Peripherals (SPI etc) can set a custom output function by calling
// lowercase configure() instead with a signal name.
p.configure(config, 256)
}
// configure is the same as Configure, but allows for setting a specific input
// or output signal.
// Signals are always routed through the GPIO matrix for simplicity. Output
// signals are configured in FUNCx_OUT_SEL_CFG which selects a particular signal
// to output on a given pin. Input signals are configured in FUNCy_IN_SEL_CFG,
// which sets the pin to use for a particular input signal.
func (p Pin) configure(config PinConfig, signal uint32) {
if p == NoPin {
// This simplifies pin configuration in peripherals such as SPI.
return
}
ioConfig := uint32(0)
// MCU_SEL: Function 1 is always GPIO
ioConfig |= (1 << esp.IO_MUX_GPIO_MCU_SEL_Pos)
// FUN_IE: Make this pin an input pin (always set for GPIO operation)
ioConfig |= esp.IO_MUX_GPIO_FUN_IE
// DRV: Set drive strength to 20 mA as a default. Pins 17 and 18 are special
var drive uint32
if p == GPIO17 || p == GPIO18 {
drive = 1 // 20 mA
} else {
drive = 2 // 20 mA
}
ioConfig |= (drive << esp.IO_MUX_GPIO_FUN_DRV_Pos)
// WPU/WPD: Select pull mode.
if config.Mode == PinInputPullup {
ioConfig |= esp.IO_MUX_GPIO_FUN_WPU
} else if config.Mode == PinInputPulldown {
ioConfig |= esp.IO_MUX_GPIO_FUN_WPD
}
// Set configuration
ioRegister := p.ioMuxReg()
ioRegister.Set(ioConfig)
switch config.Mode {
case PinOutput:
// Set the 'output enable' bit.
if p < 32 {
esp.GPIO.ENABLE_W1TS.Set(1 << p)
} else {
esp.GPIO.ENABLE1_W1TS.Set(1 << (p - 32))
}
// Set the signal to read the output value from. It can be a peripheral
// output signal, or the special value 256 which indicates regular GPIO
// usage.
p.outFunc().Set(signal)
case PinInput, PinInputPullup, PinInputPulldown:
// Clear the 'output enable' bit.
if p < 32 {
esp.GPIO.ENABLE_W1TC.Set(1 << p)
} else {
esp.GPIO.ENABLE1_W1TC.Set(1 << (p - 32))
}
if signal != 256 {
// Signal is a peripheral function (not a simple GPIO). Connect this
// signal to the pin.
// Note that outFunc and inFunc work in the opposite direction.
// outFunc configures a pin to use a given output signal, while
// inFunc specifies a pin to use to read the signal from.
inFunc(signal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(p)<<esp.GPIO_FUNC_IN_SEL_CFG_IN_SEL_Pos)
}
}
}
// ioMuxReg returns the IO_MUX_n_REG register used for configuring the io mux for
// this pin
func (p Pin) ioMuxReg() *volatile.Register32 {
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.IO_MUX.GPIO0), uintptr(p)*4))
}
// outFunc returns the FUNCx_OUT_SEL_CFG register used for configuring the
// output function selection.
func (p Pin) outFunc() *volatile.Register32 {
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.FUNC0_OUT_SEL_CFG), uintptr(p)*4))
}
// inFunc returns the FUNCy_IN_SEL_CFG register used for configuring the input
// function selection.
func inFunc(signal uint32) *volatile.Register32 {
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.FUNC0_IN_SEL_CFG), uintptr(signal)*4))
}
// Set the pin to high or low.
// Warning: only use this on an output pin!
func (p Pin) Set(value bool) {
if value {
reg, mask := p.portMaskSet()
reg.Set(mask)
} else {
reg, mask := p.portMaskClear()
reg.Set(mask)
}
}
// Return the register and mask to enable a given GPIO pin. This can be used to
// implement bit-banged drivers.
//
// Warning: only use this on an output pin!
func (p Pin) PortMaskSet() (*uint32, uint32) {
reg, mask := p.portMaskSet()
return &reg.Reg, mask
}
// Return the register and mask to disable a given GPIO pin. This can be used to
// implement bit-banged drivers.
//
// Warning: only use this on an output pin!
func (p Pin) PortMaskClear() (*uint32, uint32) {
reg, mask := p.portMaskClear()
return &reg.Reg, mask
}
func (p Pin) portMaskSet() (*volatile.Register32, uint32) {
if p < 32 {
return &esp.GPIO.OUT_W1TS, 1 << p
} else {
return &esp.GPIO.OUT1_W1TS, 1 << (p - 32)
}
}
func (p Pin) portMaskClear() (*volatile.Register32, uint32) {
if p < 32 {
return &esp.GPIO.OUT_W1TC, 1 << p
} else {
return &esp.GPIO.OUT1_W1TC, 1 << (p - 32)
}
}
// Get returns the current value of a GPIO pin when the pin is configured as an
// input or as an output.
func (p Pin) Get() bool {
if p < 32 {
return esp.GPIO.IN.Get()&(1<<p) != 0
} else {
return esp.GPIO.IN1.Get()&(1<<(p-32)) != 0
}
}
var DefaultUART = UART0
var (
UART0 = &_UART0
_UART0 = UART{Bus: esp.UART0, Buffer: NewRingBuffer()}
UART1 = &_UART1
_UART1 = UART{Bus: esp.UART1, Buffer: NewRingBuffer()}
UART2 = &_UART2
_UART2 = UART{Bus: esp.UART2, Buffer: NewRingBuffer()}
)
type UART struct {
Bus *esp.UART_Type
Buffer *RingBuffer
}
func (uart *UART) Configure(config UARTConfig) {
if config.BaudRate == 0 {
config.BaudRate = 115200
}
// Crystal clock source is selected by default
uart.Bus.CLKDIV.Set(xtalClock / config.BaudRate)
}
func (uart *UART) writeByte(b byte) error {
for (uart.Bus.STATUS.Get()>>16)&0xff >= 128 {
// Read UART_TXFIFO_CNT from the status register, which indicates how
// many bytes there are in the transmit buffer. Wait until there are
// less than 128 bytes in this buffer (the default buffer size).
}
uart.Bus.FIFO.Set(uint32(b))
return nil
}
func (uart *UART) flush() {}
// TODO: SPI
+82
View File
@@ -0,0 +1,82 @@
//go:build esp32s3
package runtime
import (
"device/esp"
)
// This is the function called on startup after the flash (IROM/DROM) is
// initialized and the stack pointer has been set.
//
//export main
func main() {
// This initialization configures the following things:
// * It disables all watchdog timers. They might be useful at some point in
// the future, but will need integration into the scheduler. For now,
// they're all disabled.
// * It sets the CPU frequency to 240MHz, which is the maximum speed allowed
// for this CPU. Lower frequencies might be possible in the future, but
// running fast and sleeping quickly is often also a good strategy to save
// power.
// TODO: protect certain memory regions, especially the area below the stack
// to protect against stack overflows. See
// esp_cpu_configure_region_protection in ESP-IDF.
// Disable RTC watchdog.
esp.RTC_CNTL.WDTWPROTECT.Set(0x50D83AA1)
esp.RTC_CNTL.WDTCONFIG0.Set(0)
esp.RTC_CNTL.WDTWPROTECT.Set(0x0) // Re-enable write protect
// Disable Timer 0 watchdog.
esp.TIMG1.WDTWPROTECT.Set(0x50D83AA1) // write protect
esp.TIMG1.WDTCONFIG0.Set(0) // disable TG0 WDT
esp.TIMG1.WDTWPROTECT.Set(0x0) // Re-enable write protect
esp.TIMG0.WDTWPROTECT.Set(0x50D83AA1) // write protect
esp.TIMG0.WDTCONFIG0.Set(0) // disable TG0 WDT
esp.TIMG0.WDTWPROTECT.Set(0x0) // Re-enable write protect
// Disable super watchdog.
esp.RTC_CNTL.SWD_WPROTECT.Set(0x8F1D312A)
esp.RTC_CNTL.SWD_CONF.Set(esp.RTC_CNTL_SWD_CONF_SWD_DISABLE)
esp.RTC_CNTL.SWD_WPROTECT.Set(0x0) // Re-enable write protect
// Change CPU frequency from 20MHz to 80MHz, by switching from the XTAL to the
// PLL clock source (see table "CPU Clock Frequency" in the reference manual).
esp.SYSTEM.SetSYSCLK_CONF_SOC_CLK_SEL(1)
// Change CPU frequency from 80MHz to 240MHz by setting SYSTEM_PLL_FREQ_SEL to
// 1 and SYSTEM_CPUPERIOD_SEL to 2 (see table "CPU Clock Frequency" in the
// reference manual).
esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(1)
esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(2)
// Clear bss. Repeat many times while we wait for cpu/clock to stabilize
for x := 0; x < 30; x++ {
clearbss()
}
// Initialize main system timer used for time.Now.
initTimer()
// Initialize the heap, call main.main, etc.
run()
// Fallback: if main ever returns, hang the CPU.
exit(0)
}
func abort() {
// lock up forever
print("abort called\n")
}
//go:extern _vector_table
var _vector_table [0]uintptr
//go:extern _sbss
var _sbss [0]byte
//go:extern _ebss
var _ebss [0]byte
+86
View File
@@ -0,0 +1,86 @@
//go:build esp32s3
package runtime
import (
"device/esp"
"machine"
"unsafe"
)
//type timeUnit int64
func putchar(c byte) {
machine.Serial.WriteByte(c)
}
func getchar() byte {
for machine.Serial.Buffered() == 0 {
Gosched()
}
v, _ := machine.Serial.ReadByte()
return v
}
func buffered() int {
return machine.Serial.Buffered()
}
// Initialize .bss: zero-initialized global variables.
// The .data section has already been loaded by the ROM bootloader.
func clearbss() {
ptr := unsafe.Pointer(&_sbss)
for ptr != unsafe.Pointer(&_ebss) {
*(*uint32)(ptr) = 0
ptr = unsafe.Add(ptr, 4)
}
}
func initTimer() {
// Configure timer 0 in timer group 0, for timekeeping.
// EN: Enable the timer.
// INCREASE: Count up every tick (as opposed to counting down).
// DIVIDER: 16-bit prescaler, set to 2 for dividing the APB clock by two
// (40MHz).
// esp.TIMG0.T0CONFIG.Set(0 << esp.TIMG_T0CONFIG_T0_EN_Pos)
esp.TIMG0.T0CONFIG.Set(esp.TIMG_TCONFIG_EN | esp.TIMG_TCONFIG_INCREASE | 2<<esp.TIMG_TCONFIG_DIVIDER_Pos)
// esp.TIMG0.T0CONFIG.Set(1 << esp.TIMG_T0CONFIG_T0_DIVCNT_RST_Pos)
// esp.TIMG0.T0CONFIG.Set(esp.TIMG_T0CONFIG_T0_EN)
// Set the timer counter value to 0.
esp.TIMG0.T0LOADLO.Set(0)
esp.TIMG0.T0LOADHI.Set(0)
esp.TIMG0.T0LOAD.Set(0) // value doesn't matter.
}
func ticks() timeUnit {
// First, update the LO and HI register pair by writing any value to the
// register. This allows reading the pair atomically.
esp.TIMG0.T0UPDATE.Set(0)
// Then read the two 32-bit parts of the timer.
return timeUnit(uint64(esp.TIMG0.T0LO.Get()) | uint64(esp.TIMG0.T0HI.Get())<<32)
}
func nanosecondsToTicks(ns int64) timeUnit {
// Calculate the number of ticks from the number of nanoseconds. At a 80MHz
// APB clock, that's 25 nanoseconds per tick with a timer prescaler of 2:
// 25 = 1e9 / (80MHz / 2)
return timeUnit(ns / 25)
}
func ticksToNanoseconds(ticks timeUnit) int64 {
// See nanosecondsToTicks.
return int64(ticks) * 25
}
// sleepTicks busy-waits until the given number of ticks have passed.
func sleepTicks(d timeUnit) {
sleepUntil := ticks() + d
for ticks() < sleepUntil {
// TODO: suspend the CPU to not burn power here unnecessarily.
}
}
func exit(code int) {
abort()
}
+21
View File
@@ -0,0 +1,21 @@
{
"inherits": ["xtensa"],
"cpu": "esp32s3",
"features": "+atomctl,+bool,+clamps,+coprocessor,+debug,+density,+div32,+esp32s3,+exception,+fp,+highpriinterrupts,+interrupt,+loop,+mac16,+memctl,+minmax,+miscsr,+mul32,+mul32high,+nsa,+prid,+regprotect,+rvector,+s32c1i,+sext,+threadptr,+timerint,+windowed",
"build-tags": ["esp32s3", "esp"],
"scheduler": "tasks",
"serial": "uart",
"linker": "ld.lld",
"default-stack-size": 2048,
"rtlib": "compiler-rt",
"libc": "picolibc",
"linkerscript": "targets/esp32s3.ld",
"extra-files": [
"src/device/esp/esp32.S",
"src/internal/task/task_stack_esp32.S"
],
"binary-format": "esp32s3",
"flash-command": "esptool.py --chip=esp32s3 --port {port} write_flash 0x0000 {bin} -ff 80m -fm dout",
"emulator": "qemu-system-xtensa -machine esp32 -nographic -drive file={img},if=mtd,format=raw",
"gdb": ["xtensa-esp32-elf-gdb"]
}
+208
View File
@@ -0,0 +1,208 @@
/* Linker script for the ESP32-S3 */
MEMORY
{
/* Note: DRAM and IRAM below are actually in the same 416K address space. */
DRAM (rw) : ORIGIN = 0x3FC88000, LENGTH = 416K /* Internal SRAM 1 (data bus) */
IRAM (x) : ORIGIN = 0x40370000, LENGTH = 416K /* Internal SRAM 1 (instruction bus) */
/* Note: DROM and IROM below are actually in the same 32M address space. */
DROM (r) : ORIGIN = 0x3C000000, LENGTH = 32M /* Data bus (read-only) */
IROM (rx) : ORIGIN = 0x42000000, LENGTH = 32M /* Instruction bus */
}
/* The entry point. It is set in the image flashed to the chip, so must be
* defined.
*/
ENTRY(call_start_cpu0)
SECTIONS
{
/* Put the stack at the bottom of DRAM, so that the application will
* crash on stack overflow instead of silently corrupting memory.
* See: http://blog.japaric.io/stack-overflow-protection/ */
.stack (NOLOAD) :
{
. = ALIGN(16);
. += _stack_size;
_stack_top = .;
} >DRAM
/* Constant literals and code. Loaded into IRAM for now. Eventually, most
* code should be executed directly from flash.
* Note that literals must be before code for the l32r instruction to work.
*/
.text.call_start_cpu0 : ALIGN(4)
{
*(.literal.call_start_cpu0)
*(.text.call_start_cpu0)
} >IRAM AT >DRAM
/* All other code and literals */
.text : ALIGN(4)
{
*(.literal .text)
*(.literal.* .text.*)
*(.text)
*(.text.*)
} >IRAM AT >DRAM
/* Constant global variables.
* They are loaded in DRAM for ease of use. Eventually they should be stored
* in flash and loaded directly from there but they're kept in RAM to make
* sure they can always be accessed (even in interrupts).
*/
.rodata : ALIGN(4)
{
*(.rodata)
*(.rodata.*)
} >DRAM
/* Mutable global variables.
*/
.data : ALIGN(4)
{
_sdata = ABSOLUTE(.);
*(.data)
*(.data.*)
_edata = ABSOLUTE(.);
} >DRAM
/* Check that the boot ROM stack (for the APP CPU) does not overlap with the
* data that is loaded by the boot ROM. There may be ways to avoid this
* issue if it occurs in practice.
* The magic value here is _stack_sentry in the boot ROM ELF file.
*/
ASSERT(_edata < 0x3ffe1320, "the .data section overlaps with the stack used by the boot ROM, possibly causing corruption at startup")
/* Global variables that are mutable and zero-initialized.
* These must be zeroed at startup (unlike data, which is loaded by the
* bootloader).
*/
.bss (NOLOAD) : ALIGN(4)
{
. = ALIGN (4);
_sbss = ABSOLUTE(.);
*(.bss)
*(.bss.*)
. = ALIGN (4);
_ebss = ABSOLUTE(.);
} >DRAM
}
/* For the garbage collector.
*/
_globals_start = _sdata;
_globals_end = _ebss;
_heap_start = _ebss;
_heap_end = ORIGIN(DRAM) + LENGTH(DRAM);
_stack_size = 4K;
/* From ESP-IDF:
* components/esp_rom/esp32/ld/esp32.rom.newlib-funcs.ld
* This is the subset that is sometimes used by LLVM during codegen, and thus
* must always be present.
*/
memset = 0x400011e8;
memcpy = 0x400011f4;
memmove = 0x40001200;
memcmp = 0x4000120c;
/* From ESP-IDF:
* components/esp_rom/esp32/ld/esp32.rom.libgcc.ld
* These are called from LLVM during codegen. The original license is Apache
* 2.0, but I believe that a list of function names and addresses can't really
* be copyrighted.
*/
__absvdi2 = 0x4000216c;
__absvsi2 = 0x40002178;
__adddf3 = 0x40002184;
__addsf3 = 0x40002190;
__addvdi3 = 0x4000219c;
__addvsi3 = 0x400021a8;
__ashldi3 = 0x400021b4;
__ashrdi3 = 0x400021c0;
__bswapdi2 = 0x400021cc;
__bswapsi2 = 0x400021d8;
__clear_cache = 0x400021e4;
__clrsbdi2 = 0x400021f0;
__clrsbsi2 = 0x400021fc;
__clzdi2 = 0x40002208;
__clzsi2 = 0x40002214;
__cmpdi2 = 0x40002220;
__ctzdi2 = 0x4000222c;
__ctzsi2 = 0x40002238;
__divdc3 = 0x40002244;
__divdf3 = 0x40002250;
__divdi3 = 0x4000225c;
__divsc3 = 0x40002268;
__divsf3 = 0x40002274;
__divsi3 = 0x40002280;
__eqdf2 = 0x4000228c;
__eqsf2 = 0x40002298;
__extendsfdf2 = 0x400022a4;
__ffsdi2 = 0x400022b0;
__ffssi2 = 0x400022bc;
__fixdfdi = 0x400022c8;
__fixdfsi = 0x400022d4;
__fixsfdi = 0x400022e0;
__fixsfsi = 0x400022ec;
__fixunsdfsi = 0x400022f8;
__fixunssfdi = 0x40002304;
__fixunssfsi = 0x40002310;
__floatdidf = 0x4000231c;
__floatdisf = 0x40002328;
__floatsidf = 0x40002334;
__floatsisf = 0x40002340;
__floatundidf = 0x4000234c;
__floatundisf = 0x40002358;
__floatunsidf = 0x40002364;
__floatunsisf = 0x40002370;
__gcc_bcmp = 0x4000237c;
__gedf2 = 0x40002388;
__gesf2 = 0x40002394;
__gtdf2 = 0x400023a0;
__gtsf2 = 0x400023ac;
__ledf2 = 0x400023b8;
__lesf2 = 0x400023c4;
__lshrdi3 = 0x400023d0;
__ltdf2 = 0x400023dc;
__ltsf2 = 0x400023e8;
__moddi3 = 0x400023f4;
__modsi3 = 0x40002400;
__muldc3 = 0x4000240c;
__muldf3 = 0x40002418;
__muldi3 = 0x40002424;
__mulsc3 = 0x40002430;
__mulsf3 = 0x4000243c;
__mulsi3 = 0x40002448;
__mulvdi3 = 0x40002454;
__mulvsi3 = 0x40002460;
__nedf2 = 0x4000246c;
__negdf2 = 0x40002478;
__negdi2 = 0x40002484;
__negsf2 = 0x40002490;
__negvdi2 = 0x4000249c;
__negvsi2 = 0x400024a8;
__nesf2 = 0x400024b4;
__paritysi2 = 0x400024c0;
__popcountdi2 = 0x400024cc;
__popcountsi2 = 0x400024d8;
__powidf2 = 0x400024e4;
__powisf2 = 0x400024f0;
__subdf3 = 0x400024fc;
__subsf3 = 0x40002508;
__subvdi3 = 0x40002514;
__subvsi3 = 0x40002520;
__truncdfsf2 = 0x4000252c;
__ucmpdi2 = 0x40002538;
__udivdi3 = 0x40002544;
__udivmoddi4 = 0x40002550;
__udivsi3 = 0x4000255c;
__udiv_w_sdiv = 0x40002568;
__umoddi3 = 0x40002574;
__umodsi3 = 0x40002580;
__unorddf2 = 0x4000258c;
__unordsf2 = 0x40002598;