Skip to content

Commit 8788c76

Browse files
committedJan 19, 2020
Enable virtual memory 128K+ via external SPI SRAM
Provides a transparently accessible additional block of RAM of 128K to 8MB by using an external SPI SRAM. This memory is managed using the UMM memory manager and can be used by the core as if it were internal RAM (albiet much slower to read or write). The use case would be for things which are quite large but not particularly frequently used or compute intensive. For example, the SSL buffers of 16K++ are a good fit for this, as are the contents of Strings (both to avoid main heap fragmentation as well as allowing Strings of >30KB). A fully associative LRU cache is used to limit the SPI bus bottleneck, and background writeback is supported. Requires `ESP.enableVM()` call to actually add the VM subsystem. If this routine is not called, then the entire VM routines should not be linked in to user apps, so there should be no space penalty w/o it. UMM `malloc` and `new` are modified to support internal and external heap regions. By default, everything comes from the standard heap, but a call to `ESP.setExternalHeap()` before the allocation (followed by a call to `ESP.resetHeap()` will make the allocation come from external RAM. See the `virtualmem.ino` example for use. If there is no external RAM installed, the `setExternalHeap` call is a no-op. The String and BearSSL libraries have been modified to use this external RAM automatically. Theory of Operation: The Xtensa core generates a hardware exception (unrelated to C++ exceptions) when an address that's defined as invalid for load or store. The XTOS ROM routines capture the machine state and call a standard C exception handler routine (or the default one which resets the system). We hook into this exception callback and decode the EXCVADDR (the address being accessed) and use the exception PC to read out the faulting instruction. We decode that instruction and simulate it's behavior (i.e. either loading or storing some data to a register/external memory) and then return to the calling application. We use the hardware SPI interface to talk to an external SRAM/PSRAM, and implement a simple cache to minimize the amount of times we need to go out over the (slow) SPI bus. The SPI is set up in a DIO mode which uses no more pins than normal SPI, but provides for ~2X faster transfers. SIO mode is also supported. NOTE: This works fine for processor accesses, but cannot be used by any of the peripherals' DMA. For that, we'd need a real MMU. Hardware Configuration (only use 3.3V compatible SRAMs!): SPI byte-addressible SRAM/PSRAM: 23LC1024 or smaller CS -> GPIO15 SCK -> GPIO14 MOSI -> GPIO13 MISO -> GPIO12 (note these are GPIO numbers, not the Arduino Dxx pin names. Refer to your ESP8266 board schematic for the mapping of GPIO to pin.) Higher density PSRAM (ESP-PSRAM64H/etc.) should work as well, but I'm still waiting on my chips so haven't done any testing. Biggest concern is their command set and functionality in DIO mode. If DIO mode isn't supported, then a fallback to SIO is possible. This PR originated with code from @pvvx's esp8266web server at https://github.com/pvvx/esp8266web (licensed in the public domain) but doesn't resemble it much any more. Thanks, @pvvx! Keep a list of the last 8 lines in RAM (~.5KB of RAM) and use that to speed up things like memcpys and other operations where the source and destination addresses are inside VM RAM. A custom set of SPI routines is used in the VM system for speed and code size (and because the core cannot be dependent on a library).
1 parent 8242d72 commit 8788c76

File tree

15 files changed

+812
-2
lines changed

15 files changed

+812
-2
lines changed
 

‎cores/esp8266/Esp.cpp

+27
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
#include "umm_malloc/umm_malloc.h"
2828
#include "cont.h"
2929
#include "coredecls.h"
30+
#include "umm_malloc/umm_malloc.h"
31+
#include "core_esp8266_vm.h"
3032

3133
extern "C" {
3234
#include "user_interface.h"
@@ -695,3 +697,28 @@ String EspClass::getSketchMD5()
695697
result = md5.toString();
696698
return result;
697699
}
700+
701+
void EspClass::enableVM()
702+
{
703+
if (!vmEnabled)
704+
install_vm_exception_handler();
705+
vmEnabled = true;
706+
}
707+
708+
void EspClass::setExternalHeap()
709+
{
710+
if (vmEnabled)
711+
umm_push_heap(UMM_HEAP_EXTERNAL);
712+
}
713+
714+
void EspClass::setInternalHeap()
715+
{
716+
if (vmEnabled)
717+
umm_push_heap(UMM_HEAP_INTERNAL);
718+
}
719+
720+
void EspClass::resetHeap()
721+
{
722+
if (vmEnabled)
723+
umm_pop_heap();
724+
}

‎cores/esp8266/Esp.h

+7
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,13 @@ class EspClass {
198198
#else
199199
uint32_t getCycleCount();
200200
#endif
201+
202+
void enableVM();
203+
void setExternalHeap();
204+
void setInternalHeap();
205+
void resetHeap();
206+
private:
207+
bool vmEnabled = false;
201208
};
202209

203210
#ifndef CORE_MOCK

‎cores/esp8266/StackThunk.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <stdlib.h>
2929
#include "StackThunk.h"
3030
#include <ets_sys.h>
31+
#include <Esp.h>
3132

3233
extern "C" {
3334

@@ -45,7 +46,9 @@ void stack_thunk_add_ref()
4546
{
4647
stack_thunk_refcnt++;
4748
if (stack_thunk_refcnt == 1) {
49+
ESP.setInternalHeap();
4850
stack_thunk_ptr = (uint32_t *)malloc(_stackSize * sizeof(uint32_t));
51+
ESP.resetHeap();
4952
stack_thunk_top = stack_thunk_ptr + _stackSize - 1;
5053
stack_thunk_save = NULL;
5154
stack_thunk_repaint();

‎cores/esp8266/WString.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,9 @@ unsigned char String::changeBuffer(unsigned int maxStrLen) {
179179
return false;
180180
}
181181
uint16_t oldLen = len();
182+
ESP.setExternalHeap();
182183
char *newbuffer = (char *) realloc(isSSO() ? nullptr : wbuffer(), newSize);
184+
ESP.resetHeap();
183185
if (newbuffer) {
184186
size_t oldSize = capacity() + 1; // include NULL.
185187
if (isSSO()) {

0 commit comments

Comments
 (0)