diff options
author | Ayke van Laethem <[email protected]> | 2021-09-04 00:55:35 +0200 |
---|---|---|
committer | Ron Evans <[email protected]> | 2021-09-16 20:13:04 +0200 |
commit | cb147b9475e843a83c2d3f4e7a19858b6cceb3c8 (patch) | |
tree | 1ab33c9b8c2ab3ab2766acc92fae8ec0f5ae9a09 | |
parent | c830f878c6effe3397aea326b5e97f6f63e9457d (diff) | |
download | tinygo-cb147b9475e843a83c2d3f4e7a19858b6cceb3c8.tar.gz tinygo-cb147b9475e843a83c2d3f4e7a19858b6cceb3c8.zip |
esp32c3: add support for this chip
This change adds support for the ESP32-C3, a new chip from Espressif. It
is a RISC-V core so porting was comparatively easy.
Most peripherals are shared with the (original) ESP32 chip, but with
subtle differences. Also, the SVD file I've used gives some
peripherals/registers a different name which makes sharing code harder.
Eventually, when an official SVD file for the ESP32 is released, I
expect that a lot of code can be shared between the two chips.
More information: https://www.espressif.com/en/products/socs/esp32-c3
TODO:
- stack scheduler
- interrupts
- most peripherals (SPI, I2C, PWM, etc)
-rw-r--r-- | Makefile | 3 | ||||
-rw-r--r-- | builder/build.go | 2 | ||||
-rw-r--r-- | builder/esp.go | 25 | ||||
m--------- | lib/cmsis-svd | 0 | ||||
-rw-r--r-- | src/device/esp/esp32c3.S | 49 | ||||
-rw-r--r-- | src/machine/machine_esp32c3.go | 144 | ||||
-rw-r--r-- | src/runtime/runtime_esp32.go | 65 | ||||
-rw-r--r-- | src/runtime/runtime_esp32c3.go | 65 | ||||
-rw-r--r-- | src/runtime/runtime_esp32xx.go | 69 | ||||
-rw-r--r-- | targets/esp32c3.json | 16 | ||||
-rw-r--r-- | targets/esp32c3.ld | 285 | ||||
-rwxr-xr-x | tools/gen-device-svd/gen-device-svd.go | 5 |
12 files changed, 659 insertions, 69 deletions
@@ -124,6 +124,7 @@ build/gen-device-svd: ./tools/gen-device-svd/*.go gen-device-esp: build/gen-device-svd ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif-Community -interrupts=software lib/cmsis-svd/data/Espressif-Community/ src/device/esp/ + ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif -interrupts=software lib/cmsis-svd/data/Espressif/ src/device/esp/ GO111MODULE=off $(GO) fmt ./src/device/esp gen-device-nrf: build/gen-device-svd @@ -432,6 +433,8 @@ ifneq ($(XTENSA), 0) $(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1 @$(MD5SUM) test.bin endif + $(TINYGO) build -size short -o test.bin -target=esp32c3 examples/serial + @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=hifive1-qemu examples/serial diff --git a/builder/build.go b/builder/build.go index c1fd78e3f..ebb1f86a0 100644 --- a/builder/build.go +++ b/builder/build.go @@ -677,7 +677,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil if err != nil { return err } - case "esp32", "esp8266": + case "esp32", "esp32c3", "esp8266": // Special format for the ESP family of chips (parsed by the ROM // bootloader). tmppath = filepath.Join(dir, "main"+outext) diff --git a/builder/esp.go b/builder/esp.go index 9a9c37f2e..98e375426 100644 --- a/builder/esp.go +++ b/builder/esp.go @@ -78,11 +78,21 @@ func makeESPFirmareImage(infile, outfile, format string) error { // An added benefit is that we don't need to check for errors all the time. outf := &bytes.Buffer{} + // Chip IDs. Source: + // https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L22 + chip_id := map[string]uint16{ + "esp32": 0x0000, + "esp32c3": 0x0005, + }[format] + // Image header. switch format { - case "esp32": + case "esp32", "esp32c3": // Header format: - // https://github.com/espressif/esp-idf/blob/8fbb63c2/components/bootloader_support/include/esp_image_format.h#L58 + // 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 + // esptool.py while flashing and therefore the hash won't be valid + // anymore. binary.Write(outf, binary.LittleEndian, struct { magic uint8 segment_count uint8 @@ -91,15 +101,18 @@ func makeESPFirmareImage(infile, outfile, format string) error { entry_addr uint32 wp_pin uint8 spi_pin_drv [3]uint8 - reserved [11]uint8 + chip_id uint16 + min_chip_rev uint8 + reserved [8]uint8 hash_appended bool }{ magic: 0xE9, segment_count: byte(len(segments)), - spi_mode: 0, // irrelevant, replaced by esptool when flashing - spi_speed_size: 0, // spi_speed, spi_size: replaced by esptool when flashing + spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO + spi_speed_size: 0x1f, // ESP_IMAGE_SPI_SPEED_80M, ESP_IMAGE_FLASH_SIZE_2MB entry_addr: uint32(inf.Entry), wp_pin: 0xEE, // disable WP pin + chip_id: chip_id, hash_appended: true, // add a SHA256 hash }) case "esp8266": @@ -142,7 +155,7 @@ func makeESPFirmareImage(infile, outfile, format string) error { outf.Write(make([]byte, 15-outf.Len()%16)) outf.WriteByte(checksum) - if format == "esp32" { + if format != "esp8266" { // SHA256 hash (to protect against image corruption, not for security). hash := sha256.Sum256(outf.Bytes()) outf.Write(hash[:]) diff --git a/lib/cmsis-svd b/lib/cmsis-svd -Subproject 9c35b6d9df1f9eeecfcc33fc6f98719dbaaa30c +Subproject df75ff974c76a911fc2815e29807f5ecaae06fc diff --git a/src/device/esp/esp32c3.S b/src/device/esp/esp32c3.S new file mode 100644 index 000000000..707339824 --- /dev/null +++ b/src/device/esp/esp32c3.S @@ -0,0 +1,49 @@ +// This is a very minimal bootloader for the ESP32-C3. It only initializes the +// flash and then continues with the generic RISC-V initialization code, which +// in turn will call runtime.main. +// It is written in assembly (and not in a higher level language) to make sure +// it is entirely loaded into IRAM and doesn't accidentally call functions +// stored in IROM. +// +// For reference, here is a nice introduction into RISC-V assembly: +// https://www.imperialviolet.org/2016/12/31/riscv.html + +.section .init +.global call_start_cpu0 +.type call_start_cpu0,@function +call_start_cpu0: + // At this point: + // - The ROM bootloader is finished and has jumped to here. + // - We're running from IRAM: both IRAM and DRAM segments have been loaded + // by the ROM bootloader. + // - We have a usable stack (but not the one we would like to use). + // - No flash mappings (MMU) are set up yet. + + // Reset MMU, see bootloader_reset_mmu in the ESP-IDF. + call Cache_Suspend_ICache + mv s0, a0 // autoload value + call Cache_Invalidate_ICache_All + call Cache_MMU_Init + + // Set up DROM from flash. + // Somehow, this also sets up IROM from flash. Not sure why, but it avoids + // the need for another such call. + // C equivalent: + // Cache_Dbus_MMU_Set(MMU_ACCESS_FLASH, 0x3C00_0000, 0, 64, 128, 0) + li a0, 0 // ext_ram: MMU_ACCESS_FLASH + li a1, 0x3C000000 // vaddr: address in the data bus + li a2, 0 // paddr: physical address in the flash chip + li a3, 64 // psize: always 64 (kilobytes) + li a4, 128 // num: pages to be set (8192K / 64K = 128) + li a5, 0 // fixed + call Cache_Dbus_MMU_Set + + // Enable the flash cache. + mv a0, s0 // restore autoload value from Cache_Suspend_ICache call + call Cache_Resume_ICache + + // Jump to generic RISC-V initialization, which initializes the stack + // pointer and globals register. It should not return. + // (It appears that the linker relaxes this jump and instead inserts the + // _start function right after here). + j _start diff --git a/src/machine/machine_esp32c3.go b/src/machine/machine_esp32c3.go new file mode 100644 index 000000000..0e45ed461 --- /dev/null +++ b/src/machine/machine_esp32c3.go @@ -0,0 +1,144 @@ +// +build esp32c3 + +package machine + +import ( + "device/esp" + "runtime/volatile" + "unsafe" +) + +// CPUFrequency returns the current CPU frequency of the chip. +// Currently it is a fixed frequency but it may allow changing in the future. +func CPUFrequency() uint32 { + return 160e6 // 160MHz +} + +const ( + PinOutput PinMode = iota + PinInput + PinInputPullup + PinInputPulldown +) + +// Configure this pin with the given configuration. +func (p Pin) Configure(config PinConfig) { + if p == NoPin { + // This simplifies pin configuration in peripherals such as SPI. + return + } + + var muxConfig uint32 + + // Configure this pin as a GPIO pin. + const function = 1 // function 1 is GPIO for every pin + muxConfig |= function << esp.IO_MUX_GPIO_MCU_SEL_Pos + + // Make this pin an input pin (always). + muxConfig |= esp.IO_MUX_GPIO_FUN_IE + + // Set drive strength: 0 is lowest, 3 is highest. + muxConfig |= 2 << esp.IO_MUX_GPIO_FUN_DRV_Pos + + // Select pull mode. + if config.Mode == PinInputPullup { + muxConfig |= esp.IO_MUX_GPIO_FUN_WPU + } else if config.Mode == PinInputPulldown { + muxConfig |= esp.IO_MUX_GPIO_FUN_WPD + } + + // Configure the pad with the given IO mux configuration. + p.mux().Set(muxConfig) + + // Set the output signal to the simple GPIO output. + p.outFunc().Set(0x80) + + switch config.Mode { + case PinOutput: + // Set the 'output enable' bit. + esp.GPIO.ENABLE_W1TS.Set(1 << p) + case PinInput, PinInputPullup, PinInputPulldown: + // Clear the 'output enable' bit. + esp.GPIO.ENABLE_W1TC.Set(1 << p) + } +} + +// 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.Pointer((uintptr(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.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.FUNC0_IN_SEL_CFG)) + uintptr(signal)*4))) +} + +// mux returns the I/O mux configuration register corresponding to the given +// GPIO pin. +func (p Pin) mux() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.IO_MUX.GPIO0)) + uintptr(p)*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, 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, mask +} + +func (p Pin) portMaskSet() (*volatile.Register32, uint32) { + return &esp.GPIO.OUT_W1TS, 1 << p +} + +func (p Pin) portMaskClear() (*volatile.Register32, uint32) { + return &esp.GPIO.OUT_W1TC, 1 << p +} + +var DefaultUART = UART0 + +var ( + UART0 = &_UART0 + _UART0 = UART{Bus: esp.UART0, Buffer: NewRingBuffer()} + UART1 = &_UART1 + _UART1 = UART{Bus: esp.UART1, Buffer: NewRingBuffer()} +) + +type UART struct { + Bus *esp.UART_Type + Buffer *RingBuffer +} + +func (uart *UART) WriteByte(b byte) error { + for (uart.Bus.STATUS.Get()&esp.UART_STATUS_TXFIFO_CNT_Msk)>>esp.UART_STATUS_TXFIFO_CNT_Pos >= 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 +} diff --git a/src/runtime/runtime_esp32.go b/src/runtime/runtime_esp32.go index 2fdeab907..79791ff22 100644 --- a/src/runtime/runtime_esp32.go +++ b/src/runtime/runtime_esp32.go @@ -6,19 +6,8 @@ import ( "device" "device/esp" "machine" - "unsafe" ) -type timeUnit int64 - -var currentTime timeUnit - -func putchar(c byte) { - machine.Serial.WriteByte(c) -} - -func postinit() {} - // This is the function called on startup right after the stack pointer has been // set. //export main @@ -50,23 +39,15 @@ func main() { // Clear .bss section. .data has already been loaded by the ROM bootloader. // Do this after increasing the CPU clock to possibly make startup slightly // faster. - preinit() + clearbss() // Initialize UART. machine.Serial.Configure(machine.UARTConfig{}) - // 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(esp.TIMG_T0CONFIG_T0_EN | esp.TIMG_T0CONFIG_T0_INCREASE | 2<<esp.TIMG_T0CONFIG_T0_DIVIDER_Pos) - - // 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. + // 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. @@ -79,44 +60,6 @@ var _sbss [0]byte //go:extern _ebss var _ebss [0]byte -func preinit() { - // Initialize .bss: zero-initialized global variables. - // The .data section has already been loaded by the ROM bootloader. - ptr := unsafe.Pointer(&_sbss) - for ptr != unsafe.Pointer(&_ebss) { - *(*uint32)(ptr) = 0 - ptr = unsafe.Pointer(uintptr(ptr) + 4) - } -} - -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 abort() { for { device.Asm("waiti 0") diff --git a/src/runtime/runtime_esp32c3.go b/src/runtime/runtime_esp32c3.go new file mode 100644 index 000000000..0f769c0fc --- /dev/null +++ b/src/runtime/runtime_esp32c3.go @@ -0,0 +1,65 @@ +// +build esp32c3 + +package runtime + +import ( + "device/esp" + "device/riscv" +) + +// 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 160MHz, 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 Timer 0 watchdog. + esp.TIMG0.WDTCONFIG0.Set(0) + + // Disable RTC watchdog. + esp.RTC_CNTL.RTC_WDTWPROTECT.Set(0x50D83AA1) + esp.RTC_CNTL.RTC_WDTCONFIG0.Set(0) + + // Disable super watchdog. + esp.RTC_CNTL.RTC_SWD_WPROTECT.Set(0x8F1D312A) + esp.RTC_CNTL.RTC_SWD_CONF.Set(esp.RTC_CNTL_RTC_SWD_CONF_SWD_DISABLE) + + // 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.SYSCLK_CONF.Set(1 << esp.SYSTEM_SYSCLK_CONF_SOC_CLK_SEL_Pos) + + // Change CPU frequency from 80MHz to 160MHz by setting SYSTEM_CPUPERIOD_SEL + // to 1 (see table "CPU Clock Frequency" in the reference manual). + // Note: we might not want to set SYSTEM_CPU_WAIT_MODE_FORCE_ON to save + // power. It is set here to keep the default on reset. + esp.SYSTEM.CPU_PER_CONF.Set(esp.SYSTEM_CPU_PER_CONF_CPU_WAIT_MODE_FORCE_ON | esp.SYSTEM_CPU_PER_CONF_PLL_FREQ_SEL | 1<<esp.SYSTEM_CPU_PER_CONF_CPUPERIOD_SEL_Pos) + + 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. + abort() +} + +func abort() { + // lock up forever + for { + riscv.Asm("wfi") + } +} diff --git a/src/runtime/runtime_esp32xx.go b/src/runtime/runtime_esp32xx.go new file mode 100644 index 000000000..c03565d15 --- /dev/null +++ b/src/runtime/runtime_esp32xx.go @@ -0,0 +1,69 @@ +// +build esp32 esp32c3 + +package runtime + +import ( + "device/esp" + "machine" + "unsafe" +) + +type timeUnit int64 + +func putchar(c byte) { + machine.Serial.WriteByte(c) +} + +func postinit() {} + +// 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.Pointer(uintptr(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(esp.TIMG_T0CONFIG_T0_EN | esp.TIMG_T0CONFIG_T0_INCREASE | 2<<esp.TIMG_T0CONFIG_T0_DIVIDER_Pos) + + // 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. + } +} diff --git a/targets/esp32c3.json b/targets/esp32c3.json new file mode 100644 index 000000000..bb7000351 --- /dev/null +++ b/targets/esp32c3.json @@ -0,0 +1,16 @@ +{ + "inherits": ["riscv32"], + "features": ["+c", "+m"], + "build-tags": ["esp32c3", "esp"], + "scheduler": "none", + "serial": "uart", + "rtlib": "compiler-rt", + "libc": "picolibc", + "linkerscript": "targets/esp32c3.ld", + "extra-files": [ + "src/device/esp/esp32c3.S" + ], + "binary-format": "esp32c3", + "flash-command": "esptool.py --chip=esp32c3 --port {port} write_flash 0x0 {bin}" +} + diff --git a/targets/esp32c3.ld b/targets/esp32c3.ld new file mode 100644 index 000000000..850a02b8e --- /dev/null +++ b/targets/esp32c3.ld @@ -0,0 +1,285 @@ +/* Linker script for the ESP32-C3 + * + * The ESP32-C3 has a rather funky memory layout, more like its Xtensa + * predecessors than like other RISC-V chips: + * - It has 384kB of regular RAM. This RAM can be used both as data RAM and + * instruction RAM, but needs to be accessed via a different address space + * (DRAM/IRAM). + * - It has another 16kB of RAM, that could be used as regular RAM but is + * normally used by the flash cache. + * - It has 8MB of address space for the DROM and IROM, but for some reason + * this address space is shared. So it isn't possible to map all DROM at + * 0x3C000000 and all DRAM at 0x42000000: they would overlap. + * - The MMU works in pages of 64kB, which means the bottom 16 bits of the + * address in flash and the address in DROM/IROM need to match. + * - Memory in DRAM and IRAM is loaded at reset by the ROM bootloader. + * Luckily, this doesn't have significant alignment requirements. + * + * This linker script has been written to carefully work around (or with) these + * limitations: + * - It adds dummy sections so that the bottom 16 bits of the virtual address + * and the physical address (in the generated firmware image) match. + * - It also offsets sections that share an address space using those same + * dummy sections. + * - It sorts the sections by load address, to avoid surprises as esptool.py + * also does it. + * This way, it's possible to create a very small firmware image that still + * conforms to the expectations of esptool.py and the ROM bootloader. + */ + +MEMORY +{ + /* Note: DRAM and IRAM below are actually in the same 384K address space. */ + DRAM (rw) : ORIGIN = 0x3FC80000, LENGTH = 384K /* Internal SRAM 1 (data bus) */ + IRAM (x) : ORIGIN = 0x40380000, LENGTH = 384K /* Internal SRAM 1 (instruction bus) */ + + /* Note: DROM and IROM below are actually in the same 8M address space. */ + DROM (r) : ORIGIN = 0x3C000000, LENGTH = 8M /* Data bus (read-only) */ + IROM (rx) : ORIGIN = 0x42000000, LENGTH = 8M /* Instruction bus */ +} + +/* The entry point. It is set in the image flashed to the chip, so must be + * defined. + */ +ENTRY(call_start_cpu0) + +SECTIONS +{ + /* Dummy section to make sure the .rodata section starts exactly behind the + * image header. + */ + .rodata_dummy (NOLOAD): ALIGN(4) + { + . += 0x18; /* image header at start of flash: esp_image_header_t */ + . += 0x8; /* DROM segment header (8 bytes) */ + } > DROM + + /* Constant global variables, stored in DROM. + */ + .rodata : ALIGN(4) + { + *(.rodata .rodata.*) + . = ALIGN (4); + } >DROM + + /* 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/ + * TODO: this might not actually work because memory protection hasn't been set up. + */ + .stack (NOLOAD) : + { + . = ALIGN(16); + . += _stack_size; + _stack_top = .; + } >DRAM + + /* 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(.); + *(.sbss) + *(.bss .bss.*) + . = ALIGN (4); + _ebss = ABSOLUTE(.); + } >DRAM + + /* Mutable global variables. This data (in the DRAM segment) is initialized + * by the ROM bootloader. + */ + .data : ALIGN(4) + { + . = ALIGN (4); + _sdata = ABSOLUTE(.); + *(.sdata) + *(.data .data.*) + . = ALIGN (4); + _edata = ABSOLUTE(.); + } >DRAM + + /* Dummy section to make sure the .init section (in the IRAM segment) is just + * behind the DRAM segment. For IRAM and DRAM, we luckily don't have to + * worry about 64kB pages or image headers as they're loaded in RAM by the + * bootloader (not mapped from flash). + */ + .iram_dummy (NOLOAD): ALIGN(4) + { + . += SIZEOF(.stack); + . += SIZEOF(.bss); + . += SIZEOF(.data); + } > IRAM + + /* Initialization code is loaded into IRAM. This memory area is also used by + * the heap, so no RAM is wasted. + */ + .init : ALIGN(4) + { + *(.init) + } >IRAM + + /* Dummy section to put the IROM segment exactly behind the IRAM segment. + * This has to follow the app image format exactly. + */ + .text_dummy (NOLOAD): ALIGN(4) + { + /* Note: DRAM and DROM are not always present so the header should only + * be inserted if it actually exists. + */ + . += 0x18; /* esp_image_header_t */ + . += SIZEOF(.rodata) + ((SIZEOF(.rodata) != 0) ? 0x8 : 0); /* DROM segment (optional) */ + . += SIZEOF(.data) + ((SIZEOF(.data) != 0) ? 0x8 : 0); /* DRAM segment (optional) */ + . += SIZEOF(.init) + 0x8; /* IRAM segment */ + . += 0x8; /* IROM segment header */ + } > IROM + + /* IROM segment. This contains all the actual code and is placed right after + * the DROM segment. + */ + .text : ALIGN(4) + { + *(.text .text.*) + } >IROM + + /DISCARD/ : + { + *(.eh_frame) /* causes 'no memory region specified' error in lld */ + } + + /* Check that the boot ROM stack (for the APP CPU) does not overlap with the + * data that is loaded by the boot ROM. This is unlikely to happen in + * practice. + * The magic value comes from here: + * https://github.com/espressif/esp-idf/blob/61299f879e/components/bootloader/subproject/main/ld/esp32c3/bootloader.ld#L191 + */ + ASSERT((_edata + SIZEOF(.init)) < 0x3FCDE710, "the .init section overlaps with the stack used by the boot ROM, possibly causing corruption at startup") +} + +/* For the garbage collector. + * Note that _heap_start starts after _edata (without caring for the .init + * section), because the .init section isn't necessary anymore after startup and + * can thus be overwritten by the heap. + */ +_globals_start = _sbss; +_globals_end = _edata; +_heap_start = _edata; +_heap_end = ORIGIN(DRAM) + LENGTH(DRAM); + +_stack_size = 4K; + +/* ROM functions used for setting up the flash mapping. + */ +Cache_Invalidate_ICache_All = 0x400004d8; +Cache_Suspend_ICache = 0x40000524; +Cache_Resume_ICache = 0x40000528; +Cache_MMU_Init = 0x4000055c; +Cache_Dbus_MMU_Set = 0x40000564; + +/* From ESP-IDF: + * components/esp_rom/esp32c3/ld/esp32c3.rom.libgcc.ld + * These are called from LLVM during codegen. The original license is Apache + * 2.0. + */ +__absvdi2 = 0x40000764; +__absvsi2 = 0x40000768; +__adddf3 = 0x4000076c; +__addsf3 = 0x40000770; +__addvdi3 = 0x40000774; +__addvsi3 = 0x40000778; +__ashldi3 = 0x4000077c; +__ashrdi3 = 0x40000780; +__bswapdi2 = 0x40000784; +__bswapsi2 = 0x40000788; +__clear_cache = 0x4000078c; +__clrsbdi2 = 0x40000790; +__clrsbsi2 = 0x40000794; +__clzdi2 = 0x40000798; +__clzsi2 = 0x4000079c; +__cmpdi2 = 0x400007a0; +__ctzdi2 = 0x400007a4; +__ctzsi2 = 0x400007a8; +__divdc3 = 0x400007ac; +__divdf3 = 0x400007b0; +__divdi3 = 0x400007b4; +__divsc3 = 0x400007b8; +__divsf3 = 0x400007bc; +__divsi3 = 0x400007c0; +__eqdf2 = 0x400007c4; +__eqsf2 = 0x400007c8; +__extendsfdf2 = 0x400007cc; +__ffsdi2 = 0x400007d0; +__ffssi2 = 0x400007d4; +__fixdfdi = 0x400007d8; +__fixdfsi = 0x400007dc; +__fixsfdi = 0x400007e0; +__fixsfsi = 0x400007e4; +__fixunsdfsi = 0x400007e8; +__fixunssfdi = 0x400007ec; +__fixunssfsi = 0x400007f0; +__floatdidf = 0x400007f4; +__floatdisf = 0x400007f8; +__floatsidf = 0x400007fc; +__floatsisf = 0x40000800; +__floatundidf = 0x40000804; +__floatundisf = 0x40000808; +__floatunsidf = 0x4000080c; +__floatunsisf = 0x40000810; +__gcc_bcmp = 0x40000814; +__gedf2 = 0x40000818; +__gesf2 = 0x4000081c; +__gtdf2 = 0x40000820; +__gtsf2 = 0x40000824; +__ledf2 = 0x40000828; +__lesf2 = 0x4000082c; +__lshrdi3 = 0x40000830; +__ltdf2 = 0x40000834; +__ltsf2 = 0x40000838; +__moddi3 = 0x4000083c; +__modsi3 = 0x40000840; +__muldc3 = 0x40000844; +__muldf3 = 0x40000848; +__muldi3 = 0x4000084c; +__mulsc3 = 0x40000850; +__mulsf3 = 0x40000854; +__mulsi3 = 0x40000858; +__mulvdi3 = 0x4000085c; +__mulvsi3 = 0x40000860; +__nedf2 = 0x40000864; +__negdf2 = 0x40000868; +__negdi2 = 0x4000086c; +__negsf2 = 0x40000870; +__negvdi2 = 0x40000874; +__negvsi2 = 0x40000878; +__nesf2 = 0x4000087c; +__paritysi2 = 0x40000880; +__popcountdi2 = 0x40000884; +__popcountsi2 = 0x40000888; +__powidf2 = 0x4000088c; +__powisf2 = 0x40000890; +__subdf3 = 0x40000894; +__subsf3 = 0x40000898; +__subvdi3 = 0x4000089c; +__subvsi3 = 0x400008a0; +__truncdfsf2 = 0x400008a4; +__ucmpdi2 = 0x400008a8; +__udivdi3 = 0x400008ac; +__udivmoddi4 = 0x400008b0; +__udivsi3 = 0x400008b4; +__udiv_w_sdiv = 0x400008b8; +__umoddi3 = 0x400008bc; +__umodsi3 = 0x400008c0; +__unorddf2 = 0x400008c4; +__unordsf2 = 0x400008c8; + +/* From ESP-IDF: + * components/esp_rom/esp32c3/ld/esp32c3.rom.newlib.ld + * These are called during codegen and thus it's a good idea to make them always + * available. ROM functions may also be faster than functions in IROM (that go + * through the flash cache) and are always available in interrupts. + */ +memset = 0x40000354; +memcpy = 0x40000358; +memmove = 0x4000035c; diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index e527be0e4..4bb9e291b 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -428,11 +428,14 @@ func readSVD(path, sourceURL string) (*Device, error) { licenseBlock = regexp.MustCompile(`\s+\n`).ReplaceAllString(licenseBlock, "\n") } + // Remove "-" characters from the device name because such characters cannot + // be used in build tags. Necessary for the ESP32-C3 for example. + nameLower := strings.ReplaceAll(strings.ToLower(device.Name), "-", "") metadata := &Metadata{ File: filepath.Base(path), DescriptorSource: sourceURL, Name: device.Name, - NameLower: strings.ToLower(device.Name), + NameLower: nameLower, Description: strings.TrimSpace(device.Description), LicenseBlock: licenseBlock, } |