Skip to content

Conversation

aykevl
Copy link
Member

@aykevl aykevl commented Aug 11, 2020

This PR adds very minimal ESP32 support. You can use it as follows:

tinygo build -o test.elf -target=esp32 -size=short examples/serial
esptool.py --chip=esp32 elf2image test.elf
esptool.py --chip=esp32 --port /dev/ttyUSB0 write_flash 0x1000 test.bin -ff 80m -fm dout

picocom --baud 115200 /dev/ttyUSB0  # optional

Support for other important things (GPIO control, tinygo flash, etc) can be added in later PRs. I've left them out to keep this as small and reviewable as possible, especially considering the LLVM branch switch.

@aykevl aykevl force-pushed the esp32 branch 4 times, most recently from 70f91be to 95932ea Compare August 12, 2020 13:15
@aykevl aykevl force-pushed the esp32 branch 9 times, most recently from 3089d5b to df56910 Compare August 13, 2020 12:59
@aykevl aykevl marked this pull request as ready for review August 13, 2020 12:59
@aykevl
Copy link
Member Author

aykevl commented Aug 13, 2020

This is now ready for review.

@aykevl aykevl requested a review from deadprogram August 13, 2020 13:53
variant:
type: string
steps:
# Cache the file because the Espressif download website is not particularly fast.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option is to download the same file from GitHub Releases, which should be pretty fast: https://github.com/espressif/crosstool-NG/releases/tag/esp-2020r2

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, thanks!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the PR with this. That makes the CI file smaller and simpler (no caches needed).

* SRAM1 has other addresses as well but the datasheet seems to indicate
* these are aliases.
*/
DRAM (rw) : ORIGIN = 0x3FFAE000, LENGTH = 200K + 128K /* Internal SRAM 1 + 2 */
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing to watch out for is that the startup stack is somewhere in that range. Might be useful to add an assert that the .data/.rodata sections don't overlap the stack used by the bootloader.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what gave me trouble initially. I solved it by setting the stack pointer at startup, see src/device/esp/esp32.S. That also makes the system more flexible, as it is no longer bound by the stack that was set by the boot ROM.

Copy link

@igrr igrr Aug 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point is that the ROM bootloader is using certain region as stack, and if the app's .data section happens to overlap the ROM bootloader stack, the bootloader will overwrite its own stack, while trying to load app's .data.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. I see. Thank you for explaining. In that case, I think there are two options:

  • Use the stack that is used by the ROM bootloader.
  • Let the application load .data instead of letting the ROM bootloader do that.

Is the bootloader stack documented somewhere?

Copy link

@igrr igrr Aug 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PRO CPU stack is at 0x3ffe1320 — 0x3ffe3f20, and APP CPU stack is at 0x3ffe5230 — 0x3ffe7e30
(I think these values are defined somewhere in IDF, but I can't find the link right now. They can be extracted from the ROM code ELF file, provided at https://dl.espressif.com/dl/esp32_rom.elf.)

In addition to that there are .bss and .data sections used by various ROM functions, at
0x3ffe0000 — 0x3ffe0440, 0x3ffe3f20 — 0x3ffe4350, 0x3ffae000 — 0x3ffae6e0. If you do not plan to call ROM functions (other than stateless functions like memcpy and memset), and do not plan to call any libraries which in turn call ROM functions, you can ignore these other reserved regions.

On the ESP32-S2, the situation is a bit more sane, as the reserved regions are near the end of DRAM, and don't make "holes" in the continuous region you would otherwise use for the application.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, that is some great insight. Also thanks for the ROM file which will be useful. I will take a look and improve this PR accordingly.
It may be possible to use the given area for the heap anyway and block out the parts that are used by the ROM code. That shouldn't need any changes to the TinyGo heap. Although I'll probably ignore that for this PR and do that only when we actually want to call these special ROM functions.

On the ESP32-S2, the situation is a bit more sane, as the reserved regions are near the end of DRAM, and don't make "holes" in the continuous region you would otherwise use for the application.

Glad to hear! Unfortunately the ESP32 will be around for a while and we'll need to adjust to it somehow.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be possible to use the given area for the heap anyway and block out the parts that are used by the ROM code.

Right, that's what we do in IDF — these ROM stack regions are getting added to the heap allocator once the startup is complete. And the regions used for ROM .data/.bss are marked as reserved.

Unfortunately the ESP32 will be around for a while and we'll need to adjust to it somehow.

Of course, it was more of me saying that we realize that reserved regions in the middle of DRAM are tricky to work with, and in the subsequent chips the situation is somewhat improved.

Copy link
Member Author

@aykevl aykevl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@igrr thanks a lot for checking this PR!

* SRAM1 has other addresses as well but the datasheet seems to indicate
* these are aliases.
*/
DRAM (rw) : ORIGIN = 0x3FFAE000, LENGTH = 200K + 128K /* Internal SRAM 1 + 2 */
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what gave me trouble initially. I solved it by setting the stack pointer at startup, see src/device/esp/esp32.S. That also makes the system more flexible, as it is no longer bound by the stack that was set by the boot ROM.

variant:
type: string
steps:
# Cache the file because the Espressif download website is not particularly fast.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, thanks!

@aykevl aykevl force-pushed the esp32 branch 2 times, most recently from ffab466 to b4595f4 Compare August 15, 2020 13:17
.long _stack_top
.global call_start_cpu0
call_start_cpu0:
l32r a1, 1b
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Xtensa window ABI changing a1 like it is done here is not sufficient, i think. If any of the previous register windows contain registers not spilled onto the stack, these registers will get saved to their original frame locations, once the window overflow happens. This might not be a practical issue, unless the boot ROM stack overlaps with app's .bss and the window overflow happens after .bss is zeroed out. If that happens, some part of .bss will contain spilled registers.

To avoid this issue, disable WOE in PS register, set WINDOWBASE to 1 << WINDOWSTART, and re-enable WOE in PS. Here is the code snippet which does this: https://github.com/espressif/esp-idf/blob/c77c4ccf6c43ab09fd89e7c907bf5cf2a3499e3b/components/xtensa/include/xt_instr_macros.h#L26

@aykevl
Copy link
Member Author

aykevl commented Aug 15, 2020

@igrr pointed out some flaws in the PR so I will mark this a draft again until I've fixed the issues.

@aykevl aykevl marked this pull request as draft August 15, 2020 19:32
@deadprogram deadprogram added this to the v0.15 milestone Aug 19, 2020
@aykevl aykevl marked this pull request as ready for review August 24, 2020 14:54
@aykevl
Copy link
Member Author

aykevl commented Aug 24, 2020

I think I've fixed all the issues pointed out by @igrr now. I'm still using a custom stack, but hopefully did the stack switch correctly this time. I've also added an assert to make sure the .data section won't overlap with the startup stack (although that should not normally happen in normal usage, with around 200kB for globals).

@deadprogram
Copy link
Member

I think the only hesitation I still have is depending on the Xtensa fork of LLVM for everything, instead of using the official LLVM repo. Perhaps we should fork the Xtensa fork ourselves, and then use that dependency, just in case something else changes again. Not sure on that, perhaps I am just being over-careful?

I will find some of my ESP32 boards for some device level testing.

@aykevl
Copy link
Member Author

aykevl commented Aug 27, 2020

Yes, good point. I have forked llvm-project into tinygo-org and pushed the branch and updated this PR accordingly, so that we have our own copy of LLVM (just to be on the safe side).

This is only very minimal support. More support (such as tinygo flash,
or peripheral access) should be added in later commits, to keep this one
focused.

Importantly, this commit changes the LLVM repo from llvm/llvm-project to
tinygo-org/llvm-project. This provides a little bit of versioning in
case something changes in the Espressif fork. If we want to upgrade to
LLVM 11 it's easy to switch back to llvm/llvm-project until Espressif
has updated their fork.
@deadprogram
Copy link
Member

Took a long time to build the LLVM. Also worth mentioning somewhere is you need to install the xtensa-esp32-elf-ld linker.

The program built the first time, and flashed/worked exactly as expected on my ESP32 board!

Seems like at this point we can merge this. We still have a lot of work to do to make it useful, but this is a massive step forward, and a long time in the making.

@aykevl
Copy link
Member Author

aykevl commented Aug 30, 2020

Also worth mentioning somewhere is you need to install the xtensa-esp32-elf-ld linker.

Ah yes, forgot to mention that. Maybe I'll make a PR to Espressif one day to implement it in ld.lld, until then we need this dependency. However, note that you can in fact use the ESP8266 toolchain too (xtensa-lx106-elf-ld), but it seemed less confusing to me to just use the ESP32 version.

@deadprogram
Copy link
Member

OK, merging. Incredible work @aykevl this is very exciting moment.

@deadprogram deadprogram merged commit 3ee47a9 into dev Aug 31, 2020
@deadprogram deadprogram deleted the esp32 branch August 31, 2020 07:18
@aykevl
Copy link
Member Author

aykevl commented Aug 31, 2020

Awesome! 🎉

@andreisfr
Copy link

Great job @aykevl !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants