diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..533944bc --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "modules/c/tlsf"] + path = modules/c/psram/tlsf + url = git@github.com:espressif/tlsf.git diff --git a/board/mpconfigboard.h b/board/mpconfigboard.h index f7553917..2001bed7 100644 --- a/board/mpconfigboard.h +++ b/board/mpconfigboard.h @@ -31,6 +31,9 @@ int mp_hal_is_pin_reserved(int n); #define MICROPY_HW_ENABLE_PSRAM (1) #define MICROPY_GC_SPLIT_HEAP (0) +// First 3MB of PSRAM for GC heap, next 4MB for PicoVector, last 1MB for tmp FS +#define MICROPY_HW_PSRAM_MAX_HEAP_SIZE (3 * 1024 * 1024) + // Alias the chip select pin specified by presto.h #define MICROPY_HW_PSRAM_CS_PIN BW_PSRAM_CS diff --git a/modules/c/picovector/micropython.cmake b/modules/c/picovector/micropython.cmake index e3c78bb8..857baafe 100644 --- a/modules/c/picovector/micropython.cmake +++ b/modules/c/picovector/micropython.cmake @@ -3,6 +3,8 @@ add_library(usermod_picovector INTERFACE) find_package(PNGDEC CONFIG REQUIRED) find_package(JPEGDEC CONFIG REQUIRED) +include(modules/c/psram/psram) + list(APPEND SOURCES ${CMAKE_CURRENT_LIST_DIR}/micropython/picovector_bindings.c ${CMAKE_CURRENT_LIST_DIR}/micropython/picovector.cpp @@ -46,7 +48,9 @@ target_include_directories(usermod_picovector INTERFACE ${CMAKE_CURRENT_LIST_DIR} ) -target_link_libraries(usermod INTERFACE usermod_picovector pngdec jpegdec) +target_compile_definitions(usermod_picovector INTERFACE PICO=1) + +target_link_libraries(usermod INTERFACE usermod_picovector pngdec jpegdec psram) set_source_files_properties( ${SOURCES} diff --git a/modules/c/picovector/micropython/image.cpp b/modules/c/picovector/micropython/image.cpp index 4a5317d1..492d7ac9 100644 --- a/modules/c/picovector/micropython/image.cpp +++ b/modules/c/picovector/micropython/image.cpp @@ -7,6 +7,7 @@ extern "C" { #include "py/reader.h" #include "py/runtime.h" #include "py/objstr.h" + #include "psram_ops.h" mp_obj_t image__del__(mp_obj_t self_in) { @@ -31,6 +32,10 @@ MPY_BIND_NEW(image, { self->image = new(m_malloc(sizeof(image_t))) image_t(bufinfo.buf, w, h); } else { self->image = new(m_malloc(sizeof(image_t))) image_t(w, h); + + // Clear new image to black, fully transparent. This should potentially + // be removed once we have a copy brush or other way to clear alpha. + psram_memset32(self->image->ptr(0, 0), 0, self->image->buffer_size() >> 2); } return MP_OBJ_FROM_PTR(self); diff --git a/modules/c/picovector/micropython/shape.cpp b/modules/c/picovector/micropython/shape.cpp index 1140e814..9b9ddd5c 100644 --- a/modules/c/picovector/micropython/shape.cpp +++ b/modules/c/picovector/micropython/shape.cpp @@ -7,7 +7,7 @@ extern "C" { mp_obj_t shape__del__(mp_obj_t self_in) { self(self_in, shape_obj_t); - m_del_class(shape_t, self->shape); + PV_DELETE(shape_t, self->shape); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(shape__del___obj, shape__del__); diff --git a/modules/c/picovector/picovector.config.hpp b/modules/c/picovector/picovector.config.hpp index e8474068..4626e1e6 100644 --- a/modules/c/picovector/picovector.config.hpp +++ b/modules/c/picovector/picovector.config.hpp @@ -1,22 +1,9 @@ -#include "micropython/mp_tracked_allocator.hpp" +#include "psram_allocator.hpp" -#ifdef __cplusplus -extern "C" { -#endif -#if MICROPY_MALLOC_USES_ALLOCATED_SIZE - void *m_malloc(size_t num_bytes); - void *m_realloc(void *ptr, size_t old_num_bytes, size_t new_num_bytes); - void m_free(void *ptr, size_t num_bytes); -#else - void *m_malloc(size_t num_bytes); - void *m_realloc(void *ptr, size_t new_num_bytes); - void m_free(void *ptr); -#endif -#ifdef __cplusplus -} -#endif +#define PV_STD_ALLOCATOR PSRAMAllocator +#define PV_MALLOC psram_malloc +#define PV_MALLOC0 psram_malloc0 +#define PV_FREE psram_free +#define PV_REALLOC psram_realloc -#define PV_STD_ALLOCATOR MPAllocator -#define PV_MALLOC m_malloc -#define PV_FREE m_free -#define PV_REALLOC m_realloc \ No newline at end of file +#define PV_DELETE(cls, ptr) ptr->~cls(); PV_FREE(ptr) \ No newline at end of file diff --git a/modules/c/picovector/picovector.cpp b/modules/c/picovector/picovector.cpp index 0893c775..400e225a 100644 --- a/modules/c/picovector/picovector.cpp +++ b/modules/c/picovector/picovector.cpp @@ -81,7 +81,7 @@ namespace picovector { } } - void build_nodes(path_t *path, rect_t *tb, mat3_t *transform, uint aa) { + void build_nodes(path_t *path, rect_t *tb, mat3_t *transform, uint32_t aa) { vec2_t offset = tb->tl(); // start with the last point to close the loop, transform it, scale for antialiasing, and offset to tile origin vec2_t last = path->points[path->points.size() - 1]; @@ -108,7 +108,7 @@ namespace picovector { uint8_t alpha_map_x4[5] = {0, 63, 127, 190, 255}; uint8_t alpha_map_x16[17] = {0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 255}; - rect_t render_nodes(rect_t *tb, uint aa) { + rect_t render_nodes(rect_t *tb, uint32_t aa) { int minx = tb->w; int miny = tb->h; int maxx = 0; @@ -170,7 +170,7 @@ namespace picovector { if(shape->paths.empty()) return; // antialias level of target image - uint aa = (uint)target->antialias(); + uint32_t aa = (uint32_t)target->antialias(); uint8_t *p_alpha_map = alpha_map_none; if(aa == 1) p_alpha_map = alpha_map_x4; @@ -279,7 +279,7 @@ namespace picovector { - void build_glyph_nodes(glyph_path_t *path, rect_t *tb, mat3_t *transform, uint aa) { + void build_glyph_nodes(glyph_path_t *path, rect_t *tb, mat3_t *transform, uint32_t aa) { vec2_t offset = tb->tl(); // start with the last point to close the loop, transform it, scale for antialiasing, and offset to tile origin glyph_path_point_t *p = &path->points[path->point_count - 1]; @@ -306,7 +306,7 @@ namespace picovector { if(!glyph->path_count) return; // antialias level of target image - uint aa = (uint)target->antialias(); + uint32_t aa = (uint32_t)target->antialias(); uint8_t *p_alpha_map = alpha_map_none; diff --git a/modules/c/psram/psram.cmake b/modules/c/psram/psram.cmake new file mode 100644 index 00000000..96dfcab1 --- /dev/null +++ b/modules/c/psram/psram.cmake @@ -0,0 +1,21 @@ + add_library(psram INTERFACE) + + target_sources(psram INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/psram_alloc.c + ${CMAKE_CURRENT_LIST_DIR}/psram_ops.c + ${CMAKE_CURRENT_LIST_DIR}/tlsf/tlsf.c + ) + + target_include_directories(psram INTERFACE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/tlsf + ${CMAKE_CURRENT_LIST_DIR}/tlsf/include + ) + + target_link_libraries(psram INTERFACE pico_platform) + + set_source_files_properties( + ${CMAKE_CURRENT_LIST_DIR}/tlsf/tlsf.c + PROPERTIES COMPILE_OPTIONS + "-O2;-fgcse-after-reload;-floop-interchange;-fpeel-loops;-fpredictive-commoning;-fsplit-paths;-ftree-loop-distribute-patterns;-ftree-loop-distribution;-ftree-vectorize;-ftree-partial-pre;-funswitch-loops" + ) diff --git a/modules/c/psram/psram_alloc.c b/modules/c/psram/psram_alloc.c new file mode 100644 index 00000000..def44eba --- /dev/null +++ b/modules/c/psram/psram_alloc.c @@ -0,0 +1,70 @@ +#include +#include "pico.h" + +#include "tlsf.h" +#include "psram_ops.h" + +#ifdef MICROPY_BUILD_TYPE +#include "py/runtime.h" +#include "py/gc.h" +#endif + +// Start 3MB in and use 4MB, leaving 1MB for a RAMFS +#define PSRAM_MALLOC_BASE 0x11300000 +#define PSRAM_MALLOC_SIZE 0x00400000 + +static tlsf_t allocator; + +void *psram_malloc(size_t num_bytes) { + if (!allocator) { + allocator = tlsf_create_with_pool((void*)PSRAM_MALLOC_BASE, PSRAM_MALLOC_SIZE, PSRAM_MALLOC_SIZE); + } + + void* ptr = tlsf_malloc(allocator, num_bytes); + +#ifdef MICROPY_BUILD_TYPE + if (!ptr) { + gc_collect(); + ptr = tlsf_malloc(allocator, num_bytes); + + if (!ptr) { + mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("PSRAM: Failed to allocate %lu bytes!"), num_bytes); + } + } +#endif + + return ptr; +} + +void *psram_malloc0(size_t num_bytes) { + // Round up to be 32-bit aligned + num_bytes = (num_bytes + 3) & ~3u; + void* ptr = psram_malloc(num_bytes); + + if (ptr) psram_memset32(ptr, 0, num_bytes >> 2); + + return ptr; +} + +void *psram_realloc(void *ptr, size_t new_num_bytes) { + if (ptr < (void*)PSRAM_MALLOC_BASE || ptr >= (void*)(PSRAM_MALLOC_BASE + PSRAM_MALLOC_SIZE)) __breakpoint(); + void* new_ptr = tlsf_realloc(allocator, ptr, new_num_bytes); + +#ifdef MICROPY_BUILD_TYPE + if (!new_ptr) { + gc_collect(); + new_ptr = tlsf_realloc(allocator, ptr, new_num_bytes); + + if (!new_ptr) { + mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("PSRAM: Failed to allocate %lu bytes!"), new_num_bytes); + } + } +#endif + + return new_ptr; +} + +void psram_free(void *ptr) { + if (ptr < (void*)PSRAM_MALLOC_BASE || ptr >= (void*)(PSRAM_MALLOC_BASE + PSRAM_MALLOC_SIZE)) __breakpoint(); + tlsf_free(allocator, ptr); +} diff --git a/modules/c/psram/psram_alloc.h b/modules/c/psram/psram_alloc.h new file mode 100644 index 00000000..9fc6af80 --- /dev/null +++ b/modules/c/psram/psram_alloc.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +void *psram_malloc(size_t num_bytes); +void *psram_malloc0(size_t num_bytes); +void *psram_realloc(void *ptr, size_t new_num_bytes); +void psram_free(void *ptr); \ No newline at end of file diff --git a/modules/c/psram/psram_allocator.hpp b/modules/c/psram/psram_allocator.hpp new file mode 100644 index 00000000..ef1798d0 --- /dev/null +++ b/modules/c/psram/psram_allocator.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include + +extern "C" { +#include "psram_alloc.h" +} + +template +struct PSRAMAllocator +{ + typedef T value_type; + + PSRAMAllocator() = default; + + template + constexpr PSRAMAllocator(const PSRAMAllocator &) noexcept {} + + [[nodiscard]] T* allocate(std::size_t n) + { + if (auto p = static_cast(psram_malloc(n * sizeof(T)))) + { + return p; + } + return NULL; + } + + void deallocate(T* p, std::size_t n) noexcept + { + psram_free(p); + } +}; + +template +bool operator==(const PSRAMAllocator &, const PSRAMAllocator &) { return true; } + +template +bool operator!=(const PSRAMAllocator &, const PSRAMAllocator &) { return false; } diff --git a/modules/c/psram/psram_ops.c b/modules/c/psram/psram_ops.c new file mode 100644 index 00000000..c8855c4e --- /dev/null +++ b/modules/c/psram/psram_ops.c @@ -0,0 +1,29 @@ +#include "psram_ops.h" + +#include + +void psram_memset32(void* ptr, uint32_t val, uint32_t num_words) { + uint32_t* ptr32 = (uint32_t*)ptr; + + // Align to 64-bit cache line + if ((uintptr_t)ptr & 4) { + *ptr32++ = val; + --num_words; + } + + // Align end to cache line + if (num_words & 1) { + ptr32[--num_words] = val; + } + + // Bulk of memset through non-cached alias, invalidating the cache + uint64_t* addr = (uint64_t*)((uint8_t*)ptr32 + 0x4000000); + const uint64_t* end = addr + (num_words >> 1); + uint8_t* cache_clean = (uint8_t*)ptr32 + 0x8000002; + uint64_t val64 = ((uint64_t)val << 32) | val; + while (addr < end) { + *addr++ = val64; + *cache_clean = 0; + cache_clean += 8; + } +} diff --git a/modules/c/psram/psram_ops.h b/modules/c/psram/psram_ops.h new file mode 100644 index 00000000..70888abd --- /dev/null +++ b/modules/c/psram/psram_ops.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +// Fast memset of 32-bit aligned memory with a 32-bit constant for large blocks of PSRAM. +void psram_memset32(void* ptr, uint32_t val, uint32_t num_words); diff --git a/modules/c/psram/tlsf b/modules/c/psram/tlsf new file mode 160000 index 00000000..19d937a5 --- /dev/null +++ b/modules/c/psram/tlsf @@ -0,0 +1 @@ +Subproject commit 19d937a530d60c605078402a318501a411904741 diff --git a/modules/common/_boot_fat.py b/modules/common/_boot_fat.py index df1b6f26..fd61b439 100644 --- a/modules/common/_boot_fat.py +++ b/modules/common/_boot_fat.py @@ -1,6 +1,7 @@ import os import rp2 import vfs +import ramfs import machine # noqa: F401 @@ -28,5 +29,7 @@ except: # noqa: E722 pass +ramfs.mkramfs() -del os, vfs, bdev, bdev_lfs, fat, lfs + +del os, vfs, ramfs, bdev, bdev_lfs, fat, lfs diff --git a/modules/common/badgeware/__init__.py b/modules/common/badgeware/__init__.py index 5b30b9b5..54325caf 100644 --- a/modules/common/badgeware/__init__.py +++ b/modules/common/badgeware/__init__.py @@ -54,6 +54,7 @@ def __call__(self, update): return display.update() + gc.collect() badge.poll() if self.duration is not None and self.ticks >= self.duration: diff --git a/modules/common/ramfs.py b/modules/common/ramfs.py new file mode 100644 index 00000000..28d4e653 --- /dev/null +++ b/modules/common/ramfs.py @@ -0,0 +1,66 @@ +import vfs + +@micropython.viper +def viper_memcpy(dest: ptr8, src: ptr8, num: int) -> int: + for i in range(num): + dest[i] = src[i] + return num + + +class RAMFS: + RAMFS_BASE = 0x11700000 + RAMFS_SIZE = 0x00100000 + + def __init__(self, size=0x100000, offset=0, blocksize=256, debug=False): + self.debug = debug + self.blocks, remainder = divmod(size, blocksize) + + if remainder: + raise ValueError("Size should be a multiple of {blocksize:0,d}") + + self.blocksize = blocksize + + if size + offset > self.RAMFS_SIZE: + raise ValueError("Size requested is larger than reserved RAM") + + self.address = self.RAMFS_BASE + offset + self.length = size + + def readblocks(self, block_num, buf, offset=0): + if self.debug: + print(f"PSRAM: readblocks: {block_num} {len(buf)}, {offset}") + viper_memcpy(buf, self.address + (block_num * self.blocksize) + offset, len(buf)) + + def writeblocks(self, block_num, buf, offset=0): + if self.debug: + print(f"PSRAM: writeblocks: {block_num} {len(buf)}, {offset}") + viper_memcpy(self.address + (block_num * self.blocksize) + offset, buf, len(buf)) + + def ioctl(self, op, arg): + if self.debug: + print(f"PSRAM: ioctl: {op} {arg}") + if op == 3: # Sync + return 0 + if op == 4: # Block Count + return self.blocks + if op == 5: # Block Size + return self.blocksize + if op == 6: # Erase + # We don't need to erase blocks ever, + # but it might be worth implementing? + return 0 + return None + + def __str__(self): + return f"RAMFS: length: {self.length}, address: {self.address}" + +def mkramfs(size=1024 * 1024, mount_point="/tmp", debug=False): + psram = RAMFS(size, debug=debug) + + try: + fs = vfs.VfsLfs2(psram, progsize=256) + except OSError: + vfs.VfsLfs2.mkfs(psram, progsize=256) + fs = vfs.VfsLfs2(psram, progsize=256) + + vfs.mount(fs, mount_point)