aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2021-09-04 00:55:35 +0200
committerRon Evans <[email protected]>2021-09-16 20:13:04 +0200
commitcb147b9475e843a83c2d3f4e7a19858b6cceb3c8 (patch)
tree1ab33c9b8c2ab3ab2766acc92fae8ec0f5ae9a09
parentc830f878c6effe3397aea326b5e97f6f63e9457d (diff)
downloadtinygo-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--Makefile3
-rw-r--r--builder/build.go2
-rw-r--r--builder/esp.go25
m---------lib/cmsis-svd0
-rw-r--r--src/device/esp/esp32c3.S49
-rw-r--r--src/machine/machine_esp32c3.go144
-rw-r--r--src/runtime/runtime_esp32.go65
-rw-r--r--src/runtime/runtime_esp32c3.go65
-rw-r--r--src/runtime/runtime_esp32xx.go69
-rw-r--r--targets/esp32c3.json16
-rw-r--r--targets/esp32c3.ld285
-rwxr-xr-xtools/gen-device-svd/gen-device-svd.go5
12 files changed, 659 insertions, 69 deletions
diff --git a/Makefile b/Makefile
index 4f8284304..fd3dd8838 100644
--- a/Makefile
+++ b/Makefile
@@ -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.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) {
+ 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,
}