From 28901b2c300e8c9dacd2236502bb00c5f3f24958 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 21 Apr 2025 17:07:30 +1000 Subject: [PATCH 001/154] all: Bump version to 1.26.0-preview. Signed-off-by: Damien George --- py/mpconfig.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py/mpconfig.h b/py/mpconfig.h index dcf0e4f05c97f..d06932a77b742 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -30,9 +30,9 @@ // as well as a fallback to generate MICROPY_GIT_TAG if the git repo or tags // are unavailable. #define MICROPY_VERSION_MAJOR 1 -#define MICROPY_VERSION_MINOR 25 +#define MICROPY_VERSION_MINOR 26 #define MICROPY_VERSION_MICRO 0 -#define MICROPY_VERSION_PRERELEASE 0 +#define MICROPY_VERSION_PRERELEASE 1 // Combined version as a 32-bit number for convenience to allow version // comparison. Doesn't include prerelease state. From 760b962924ef5df72310832f87552341565d9325 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 27 Mar 2025 21:29:14 -0500 Subject: [PATCH 002/154] tests/basics/builtin_range.py: Add more tests for range slicing. Signed-off-by: Jeff Epler --- tests/basics/builtin_range.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/basics/builtin_range.py b/tests/basics/builtin_range.py index 66226bad16a6d..728679bc9a1fc 100644 --- a/tests/basics/builtin_range.py +++ b/tests/basics/builtin_range.py @@ -32,11 +32,27 @@ print(range(1, 4)[0:]) print(range(1, 4)[1:]) print(range(1, 4)[:-1]) +print(range(4, 1)[:]) +print(range(4, 1)[0:]) +print(range(4, 1)[1:]) +print(range(4, 1)[:-1]) print(range(7, -2, -4)[:]) print(range(1, 100, 5)[5:15:3]) print(range(1, 100, 5)[15:5:-3]) print(range(100, 1, -5)[5:15:3]) print(range(100, 1, -5)[15:5:-3]) +print(range(1, 100, 5)[5:15:-3]) +print(range(1, 100, 5)[15:5:3]) +print(range(100, 1, -5)[5:15:-3]) +print(range(100, 1, -5)[15:5:3]) +print(range(1, 100, 5)[5:15:3]) +print(range(1, 100, 5)[15:5:-3]) +print(range(1, 100, -5)[5:15:3]) +print(range(1, 100, -5)[15:5:-3]) +print(range(1, 100, 5)[5:15:-3]) +print(range(1, 100, 5)[15:5:3]) +print(range(1, 100, -5)[5:15:-3]) +print(range(1, 100, -5)[15:5:3]) # for this case uPy gives a different stop value but the listed elements are still correct print(list(range(7, -2, -4)[2:-2:])) From 8faa6bafdc76a6ee307551ef4d88fd2d54db04b2 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 27 Mar 2025 21:29:01 -0500 Subject: [PATCH 003/154] py/objrange: Match CPython range slicing. The "index fixing" behavior of get_fast_slice_indexes are not desired here; the underlying behavior of mp_obj_slice_indexes actually is. Fixes issue #17016. Signed-off-by: Jeff Epler --- py/objrange.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/py/objrange.c b/py/objrange.c index 5ccb04fba60c8..1cc575f33e763 100644 --- a/py/objrange.c +++ b/py/objrange.c @@ -164,15 +164,11 @@ static mp_obj_t range_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { #if MICROPY_PY_BUILTINS_SLICE if (mp_obj_is_type(index, &mp_type_slice)) { mp_bound_slice_t slice; - mp_seq_get_fast_slice_indexes(len, index, &slice); + mp_obj_slice_indices(index, len, &slice); mp_obj_range_t *o = mp_obj_malloc(mp_obj_range_t, &mp_type_range); o->start = self->start + slice.start * self->step; o->stop = self->start + slice.stop * self->step; o->step = slice.step * self->step; - if (slice.step < 0) { - // Negative slice steps have inclusive stop, so adjust for exclusive - o->stop -= self->step; - } return MP_OBJ_FROM_PTR(o); } #endif From 0d2c18c299936a34cdeb43e2ef9cbdaecb35c610 Mon Sep 17 00:00:00 2001 From: Yoctopuce dev Date: Thu, 20 Mar 2025 12:03:59 +0100 Subject: [PATCH 004/154] py/objstr: Fix handling of OP_MODULO with namedtuple. This fix handles attrtuple as well, eg. os.uname(). A test case has been added in basics/attrtuple2.py. Fixes issue #16969. Signed-off-by: Yoctopuce dev --- py/objstr.c | 4 ++-- py/objtuple.c | 3 --- py/objtuple.h | 3 +++ tests/basics/attrtuple2.py | 25 +++++++++++++++++++++++++ tests/basics/namedtuple1.py | 3 +++ 5 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 tests/basics/attrtuple2.py diff --git a/py/objstr.c b/py/objstr.c index 307e956a15236..a160ab415c6cf 100644 --- a/py/objstr.c +++ b/py/objstr.c @@ -33,6 +33,7 @@ #include "py/objlist.h" #include "py/runtime.h" #include "py/cstack.h" +#include "py/objtuple.h" #if MICROPY_PY_BUILTINS_STR_OP_MODULO static mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_t *args, mp_obj_t dict); @@ -357,8 +358,7 @@ mp_obj_t mp_obj_str_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i mp_obj_t *args = &rhs_in; size_t n_args = 1; mp_obj_t dict = MP_OBJ_NULL; - if (mp_obj_is_type(rhs_in, &mp_type_tuple)) { - // TODO: Support tuple subclasses? + if (mp_obj_is_tuple_compatible(rhs_in)) { mp_obj_tuple_get(rhs_in, &n_args, &args); } else if (mp_obj_is_type(rhs_in, &mp_type_dict)) { dict = rhs_in; diff --git a/py/objtuple.c b/py/objtuple.c index 2cbcc0e502f9a..a8bd11c7e2735 100644 --- a/py/objtuple.c +++ b/py/objtuple.c @@ -31,9 +31,6 @@ #include "py/objtuple.h" #include "py/runtime.h" -// type check is done on getiter method to allow tuple, namedtuple, attrtuple -#define mp_obj_is_tuple_compatible(o) (MP_OBJ_TYPE_GET_SLOT_OR_NULL(mp_obj_get_type(o), iter) == mp_obj_tuple_getiter) - /******************************************************************************/ /* tuple */ diff --git a/py/objtuple.h b/py/objtuple.h index cc42aa6df3980..034814b82662d 100644 --- a/py/objtuple.h +++ b/py/objtuple.h @@ -61,4 +61,7 @@ void mp_obj_attrtuple_print_helper(const mp_print_t *print, const qstr *fields, mp_obj_t mp_obj_new_attrtuple(const qstr *fields, size_t n, const mp_obj_t *items); +// type check is done on getiter method to allow tuple, namedtuple, attrtuple +#define mp_obj_is_tuple_compatible(o) (MP_OBJ_TYPE_GET_SLOT_OR_NULL(mp_obj_get_type(o), iter) == mp_obj_tuple_getiter) + #endif // MICROPY_INCLUDED_PY_OBJTUPLE_H diff --git a/tests/basics/attrtuple2.py b/tests/basics/attrtuple2.py new file mode 100644 index 0000000000000..081d24b6ae92c --- /dev/null +++ b/tests/basics/attrtuple2.py @@ -0,0 +1,25 @@ +# test os.uname() attrtuple, if available +try: + import os +except ImportError: + print("SKIP") + raise SystemExit + +try: + u = os.uname() +except AttributeError: + print("SKIP") + raise SystemExit + +# test printing of attrtuple +print(str(u).find("machine=") > 0) + +# test read attr +print(isinstance(u.machine, str)) + +# test str modulo operator for attrtuple +impl_str = ("%s " * len(u)) % u +test_str = "" +for val in u: + test_str += val + " " +print(impl_str == test_str) diff --git a/tests/basics/namedtuple1.py b/tests/basics/namedtuple1.py index 362c60583ec32..b9689b58423b8 100644 --- a/tests/basics/namedtuple1.py +++ b/tests/basics/namedtuple1.py @@ -21,6 +21,9 @@ print(isinstance(t, tuple)) + # a NamedTuple can be used as a tuple + print("(%d, %d)" % t) + # Check tuple can compare equal to namedtuple with same elements print(t == (t[0], t[1]), (t[0], t[1]) == t) From 52ca8268800416538368dea1976d0922ff07543f Mon Sep 17 00:00:00 2001 From: Yoctopuce dev Date: Thu, 20 Mar 2025 12:03:59 +0100 Subject: [PATCH 005/154] unix/variants: Enable os.uname() in coverage build for tests. In order to provide test coverage for the previous commit, `os.uname()` support is added to the unix coverage build. Signed-off-by: Yoctopuce dev --- ports/unix/variants/coverage/mpconfigvariant.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ports/unix/variants/coverage/mpconfigvariant.h b/ports/unix/variants/coverage/mpconfigvariant.h index a72e533e74c4a..04b5b8ae1ae70 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.h +++ b/ports/unix/variants/coverage/mpconfigvariant.h @@ -44,3 +44,10 @@ #undef MICROPY_VFS_ROM_IOCTL #define MICROPY_VFS_ROM_IOCTL (1) #define MICROPY_PY_CRYPTOLIB_CTR (1) + +// Enable os.uname for attrtuple coverage test +#define MICROPY_PY_OS_UNAME (1) +#define MICROPY_HW_BOARD_NAME "a machine" +#define MICROPY_HW_MCU_NAME MICROPY_PY_SYS_PLATFORM +// Keep the standard banner message +#define MICROPY_BANNER_MACHINE MICROPY_PY_SYS_PLATFORM " [" MICROPY_PLATFORM_COMPILER "] version" From 6025b78d01a883ed24c2f983c1d7b6a4a05d3774 Mon Sep 17 00:00:00 2001 From: David Yang Date: Fri, 21 Mar 2025 10:25:40 +0800 Subject: [PATCH 006/154] unix/main: Remove PATH_MAX from realpath. POSIX.1-2008 ensures realpath() give a dynamically allocated buffer if NULL is passed (which is also true for ports/windows/realpath.c), avoiding an explicit call to malloc() and use of PATH_MAX, which may be undefined on some systems. Signed-off-by: David Yang --- ports/unix/main.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/ports/unix/main.c b/ports/unix/main.c index 0d137fe1b58ea..9f51573fbfbe9 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -453,10 +453,13 @@ static void set_sys_argv(char *argv[], int argc, int start_arg) { #if MICROPY_PY_SYS_EXECUTABLE extern mp_obj_str_t mp_sys_executable_obj; -static char executable_path[MICROPY_ALLOC_PATH_MAX]; +static char *executable_path = NULL; static void sys_set_excecutable(char *argv0) { - if (realpath(argv0, executable_path)) { + if (executable_path == NULL) { + executable_path = realpath(argv0, NULL); + } + if (executable_path != NULL) { mp_obj_str_set_data(&mp_sys_executable_obj, (byte *)executable_path, strlen(executable_path)); } } @@ -721,11 +724,9 @@ MP_NOINLINE int main_(int argc, char **argv) { return invalid_args(); } } else { - char *pathbuf = malloc(PATH_MAX); - char *basedir = realpath(argv[a], pathbuf); + char *basedir = realpath(argv[a], NULL); if (basedir == NULL) { mp_printf(&mp_stderr_print, "%s: can't open file '%s': [Errno %d] %s\n", argv[0], argv[a], errno, strerror(errno)); - free(pathbuf); // CPython exits with 2 in such case ret = 2; break; @@ -734,7 +735,7 @@ MP_NOINLINE int main_(int argc, char **argv) { // Set base dir of the script as first entry in sys.path. char *p = strrchr(basedir, '/'); mp_obj_list_store(mp_sys_path, MP_OBJ_NEW_SMALL_INT(0), mp_obj_new_str_via_qstr(basedir, p - basedir)); - free(pathbuf); + free(basedir); set_sys_argv(argv, argc, a); ret = do_file(argv[a]); @@ -800,6 +801,11 @@ MP_NOINLINE int main_(int argc, char **argv) { #endif #endif + #if MICROPY_PY_SYS_EXECUTABLE && !defined(NDEBUG) + // Again, make memory leak detector happy + free(executable_path); + #endif + // printf("total bytes = %d\n", m_get_total_bytes_allocated()); return ret & 0xff; } From 38a3873310c571c32e52edc2a7d4de7c80ff35d5 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Mon, 18 Nov 2024 05:35:48 +0100 Subject: [PATCH 007/154] unix/mpthreadport: Work around lack of thread cancellation on Android. This commit fixes thread-related compilation issues under Android using Termux as its runtime environment. On Android's libc (Bionic) thread cancellation is not implemented, but the Unix port uses that mechanism to provide asynchronous thread termination. In this commit there is a workaround for that, by adding a new signal handler to each newly created thread, whose callback simply exits the thread. Threads are then sent the new signal rather than being explicitly cancelled, which in turn trigger the signal handler to stop the thread execution at the next possible occasion. This makes the cancellation behaviour differ slightly on Android, as threads are probably going to linger a little bit more since the method introduced in this commit is equivalent to setting PTHREAD_CANCEL_DEFERRED as the thread cancellation type. On the other hand there are no guarantees of immediate cancellation using PTHREAD_CANCEL_ASYNCHRONOUS either. This fixes the pthread-related issues reported in #16259. Signed-off-by: Alessandro Gatti --- ports/unix/mpthreadport.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/ports/unix/mpthreadport.c b/ports/unix/mpthreadport.c index 5172645bc147a..ded3bd14aff70 100644 --- a/ports/unix/mpthreadport.c +++ b/ports/unix/mpthreadport.c @@ -45,8 +45,14 @@ // potential conflict with other uses of the more commonly used SIGUSR1. #ifdef SIGRTMIN #define MP_THREAD_GC_SIGNAL (SIGRTMIN + 5) +#ifdef __ANDROID__ +#define MP_THREAD_TERMINATE_SIGNAL (SIGRTMIN + 6) +#endif #else #define MP_THREAD_GC_SIGNAL (SIGUSR1) +#ifdef __ANDROID__ +#define MP_THREAD_TERMINATE_SIGNAL (SIGUSR2) +#endif #endif // This value seems to be about right for both 32-bit and 64-bit builds. @@ -107,6 +113,18 @@ static void mp_thread_gc(int signo, siginfo_t *info, void *context) { } } +// On Android, pthread_cancel and pthread_setcanceltype are not implemented. +// To achieve that result a new signal handler responding on either +// (SIGRTMIN + 6) or SIGUSR2 is installed on every child thread. The sole +// purpose of this new signal handler is to terminate the thread in a safe +// asynchronous manner. + +#ifdef __ANDROID__ +static void mp_thread_terminate(int signo, siginfo_t *info, void *context) { + pthread_exit(NULL); +} +#endif + void mp_thread_init(void) { pthread_key_create(&tls_key, NULL); pthread_setspecific(tls_key, &mp_state_ctx.thread); @@ -135,6 +153,14 @@ void mp_thread_init(void) { sa.sa_sigaction = mp_thread_gc; sigemptyset(&sa.sa_mask); sigaction(MP_THREAD_GC_SIGNAL, &sa, NULL); + + // Install a signal handler for asynchronous termination if needed. + #if defined(__ANDROID__) + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = mp_thread_terminate; + sigemptyset(&sa.sa_mask); + sigaction(MP_THREAD_TERMINATE_SIGNAL, &sa, NULL); + #endif } void mp_thread_deinit(void) { @@ -142,7 +168,11 @@ void mp_thread_deinit(void) { while (thread->next != NULL) { mp_thread_t *th = thread; thread = thread->next; + #if defined(__ANDROID__) + pthread_kill(th->id, MP_THREAD_TERMINATE_SIGNAL); + #else pthread_cancel(th->id); + #endif free(th); } mp_thread_unix_end_atomic_section(); @@ -200,7 +230,9 @@ void mp_thread_start(void) { } #endif + #if !defined(__ANDROID__) pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + #endif mp_thread_unix_begin_atomic_section(); for (mp_thread_t *th = thread; th != NULL; th = th->next) { if (th->id == pthread_self()) { From 27f4351f5fcd649fde12314d874083fcf967c01b Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 19 Mar 2025 11:38:29 +1100 Subject: [PATCH 008/154] CODECONVENTIONS: Document the static naming conventions. Goal is to document what's most commonly already in use, not to come up with a new standard. Also reformat the doc a bit for easier deep linking. Signed-off-by: Angus Gratton --- CODECONVENTIONS.md | 52 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/CODECONVENTIONS.md b/CODECONVENTIONS.md index d6af0418e42ef..d3f71cb083deb 100644 --- a/CODECONVENTIONS.md +++ b/CODECONVENTIONS.md @@ -206,14 +206,21 @@ adhere to the existing style and use `tools/codeformat.py` to check any changes. The main conventions, and things not enforceable via the auto-formatter, are described below. -White space: +As the MicroPython code base is over ten years old, not every source file +conforms fully to these conventions. If making small changes to existing code, +then it's usually acceptable to follow the existing code's style. New code or +major changes should follow the conventions described here. + +## White space + - Expand tabs to 4 spaces. - Don't leave trailing whitespace at the end of a line. - For control blocks (if, for, while), put 1 space between the keyword and the opening parenthesis. - Put 1 space after a comma, and 1 space around operators. -Braces: +## Braces + - Use braces for all blocks, even no-line and single-line pieces of code. - Put opening braces on the end of the line it belongs to, not on @@ -221,18 +228,43 @@ Braces: - For else-statements, put the else on the same line as the previous closing brace. -Header files: +## Header files + - Header files should be protected from multiple inclusion with #if directives. See an existing header for naming convention. -Names: +## Names + - Use underscore_case, not camelCase for all names. - Use CAPS_WITH_UNDERSCORE for enums and macros. - When defining a type use underscore_case and put '_t' after it. -Integer types: MicroPython runs on 16, 32, and 64 bit machines, so it's -important to use the correctly-sized (and signed) integer types. The -general guidelines are: +### Public names (declared in headers) + +- MicroPython-specific names (especially any declared in `py/` and `extmod/` + directories) should generally start with `mp_` or `MP_`. +- Functions and variables declared in a header should generally share a longer + common prefix. Usually the prefix matches the file name (i.e. items defined in + `py/obj.c` are declared in `py/obj.h` and should be prefixed `mp_obj_`). There + are exceptions, for example where one header file contains declarations + implemented in multiple source files for expediency. + +### Private names (specific to a single .c file) + +- For static functions and variables exposed to Python (i.e. a static C function + that is wrapped in `MP_DEFINE_CONST_FUN_...` and attached to a module), use + the file-level shared common prefix, i.e. name them as if the function or + variable was not static. +- Other static definitions in source files (i.e. functions or variables defined + in a .c file that are only used within that .c file) don't need any prefix + (specifically: no `s_` or `_` prefix, and generally avoid adding the + file-level common prefix). + +## Integer types + +MicroPython runs on 16, 32, and 64 bit machines, so it's important to use the +correctly-sized (and signed) integer types. The general guidelines are: + - For most cases use mp_int_t for signed and mp_uint_t for unsigned integer values. These are guaranteed to be machine-word sized and therefore big enough to hold the value from a MicroPython small-int @@ -241,11 +273,13 @@ general guidelines are: - You can use int/uint, but remember that they may be 16-bits wide. - If in doubt, use mp_int_t/mp_uint_t. -Comments: +## Comments + - Be concise and only write comments for things that are not obvious. - Use `// ` prefix, NOT `/* ... */`. No extra fluff. -Memory allocation: +## Memory allocation + - Use m_new, m_renew, m_del (and friends) to allocate and free heap memory. These macros are defined in py/misc.h. From ba4179bb66ad2dabc385de4584e7b57c9bffb347 Mon Sep 17 00:00:00 2001 From: dubiousjim Date: Thu, 20 Mar 2025 05:55:55 -0400 Subject: [PATCH 009/154] py/dynruntime.mk: Fix use of musl's libm.a when LINK_RUNTIME=1. Like PICOLIBC, MUSL also has its math functions in libc.a. There is a libm.a, but it's empty. Signed-off-by: dubiousjim --- py/dynruntime.mk | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/py/dynruntime.mk b/py/dynruntime.mk index 807befb464a84..1ef521bd9aecc 100644 --- a/py/dynruntime.mk +++ b/py/dynruntime.mk @@ -124,6 +124,10 @@ else $(error architecture '$(ARCH)' not supported) endif +ifneq ($(findstring -musl,$(shell $(CROSS)gcc -dumpmachine)),) +USE_MUSL := 1 +endif + MICROPY_FLOAT_IMPL_UPPER = $(shell echo $(MICROPY_FLOAT_IMPL) | tr '[:lower:]' '[:upper:]') CFLAGS += $(CFLAGS_ARCH) -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_$(MICROPY_FLOAT_IMPL_UPPER) @@ -147,6 +151,8 @@ ifeq ($(LINK_RUNTIME),1) # distribution. ifeq ($(USE_PICOLIBC),1) LIBM_NAME := libc.a +else ifeq ($(USE_MUSL),1) +LIBM_NAME := libc.a else LIBM_NAME := libm.a endif From 7c7a9bdb344bd83db9bc4e5a41114283a624095c Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 26 Mar 2025 09:43:50 +1100 Subject: [PATCH 010/154] drivers/ninaw10/machine_pin_nina: Add exception text wrappers. Required in MICROPY_PREVIEW_VERSION_2. Signed-off-by: Andrew Leech --- drivers/ninaw10/machine_pin_nina.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/ninaw10/machine_pin_nina.c b/drivers/ninaw10/machine_pin_nina.c index c72ecee3dbb3e..fe6eb5c03af39 100644 --- a/drivers/ninaw10/machine_pin_nina.c +++ b/drivers/ninaw10/machine_pin_nina.c @@ -114,13 +114,13 @@ void machine_pin_ext_config(machine_pin_obj_t *self, int mode, int value) { mode = NINA_GPIO_OUTPUT; self->is_output = true; } else { - mp_raise_ValueError("only Pin.OUT and Pin.IN are supported for this pin"); + mp_raise_ValueError(MP_ERROR_TEXT("only Pin.OUT and Pin.IN are supported for this pin")); } if (self->id >= 0 && self->id < MICROPY_HW_PIN_EXT_COUNT) { uint8_t buf[] = {pin_map[self->id], mode}; if (mode == NINA_GPIO_OUTPUT) { if (NINA_GPIO_IS_INPUT_ONLY(self->id)) { - mp_raise_ValueError("only Pin.IN is supported for this pin"); + mp_raise_ValueError(MP_ERROR_TEXT("only Pin.IN is supported for this pin")); } machine_pin_ext_set(self, value); } From b6dbc47664e0b5dc3516f59a24322d26d2ad2617 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 26 Mar 2025 10:19:40 +1100 Subject: [PATCH 011/154] extmod/machine_usb_device: Add exception text wrappers. Required in MICROPY_PREVIEW_VERSION_2. Signed-off-by: Andrew Leech --- extmod/machine_usb_device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extmod/machine_usb_device.c b/extmod/machine_usb_device.c index 5c4e84c38d1fe..5019cd98779f5 100644 --- a/extmod/machine_usb_device.c +++ b/extmod/machine_usb_device.c @@ -108,7 +108,7 @@ static mp_obj_t usb_device_submit_xfer(mp_obj_t self, mp_obj_t ep, mp_obj_t buff // // This C layer doesn't otherwise keep track of which endpoints the host // is aware of (or not). - mp_raise_ValueError("ep"); + mp_raise_ValueError(MP_ERROR_TEXT("ep")); } if (!usbd_edpt_claim(USBD_RHPORT, ep_addr)) { From 9bde1970044b7b0a6667d2f4a675c8c3f7816706 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 26 Mar 2025 10:19:53 +1100 Subject: [PATCH 012/154] rp2: Add exception text wrappers. Required in MICROPY_PREVIEW_VERSION_2. Signed-off-by: Andrew Leech --- ports/rp2/machine_pin.c | 6 +++--- ports/rp2/machine_pin_cyw43.c | 2 +- ports/rp2/rp2_pio.c | 18 +++++++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ports/rp2/machine_pin.c b/ports/rp2/machine_pin.c index 8ba0b44a684f7..d9ca3f8ce37a5 100644 --- a/ports/rp2/machine_pin.c +++ b/ports/rp2/machine_pin.c @@ -193,7 +193,7 @@ const machine_pin_obj_t *machine_pin_find(mp_obj_t pin) { return &machine_pin_obj_table[wanted_pin]; } } - mp_raise_ValueError("invalid pin"); + mp_raise_ValueError(MP_ERROR_TEXT("invalid pin")); } static void machine_pin_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { @@ -259,11 +259,11 @@ static mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_ mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); if (is_ext_pin(self) && args[ARG_pull].u_obj != mp_const_none) { - mp_raise_ValueError("pulls are not supported for external pins"); + mp_raise_ValueError(MP_ERROR_TEXT("pulls are not supported for external pins")); } if (is_ext_pin(self) && args[ARG_alt].u_int != GPIO_FUNC_SIO) { - mp_raise_ValueError("alternate functions are not supported for external pins"); + mp_raise_ValueError(MP_ERROR_TEXT("alternate functions are not supported for external pins")); } // get initial value of pin (only valid for OUT and OPEN_DRAIN modes) diff --git a/ports/rp2/machine_pin_cyw43.c b/ports/rp2/machine_pin_cyw43.c index 9fc262454f86c..f79d932f7a535 100644 --- a/ports/rp2/machine_pin_cyw43.c +++ b/ports/rp2/machine_pin_cyw43.c @@ -70,7 +70,7 @@ void machine_pin_ext_config(machine_pin_obj_t *self, int mode, int value) { self->is_output = true; } } else { - mp_raise_ValueError("only Pin.OUT and Pin.IN are supported for this pin"); + mp_raise_ValueError(MP_ERROR_TEXT("only Pin.OUT and Pin.IN are supported for this pin")); } if (value != -1) { diff --git a/ports/rp2/rp2_pio.c b/ports/rp2/rp2_pio.c index 56c576581e0df..d936553b55573 100644 --- a/ports/rp2/rp2_pio.c +++ b/ports/rp2/rp2_pio.c @@ -126,7 +126,7 @@ void rp2_pio_irq_set_exclusive_handler(PIO pio, uint irq) { irq_handler_t current = irq_get_exclusive_handler(irq); // If the IRQ is set and isn't our handler, or a shared handler is set, then raise an error if ((current && current != rp2_pio_get_irq_handler(pio)) || irq_has_shared_handler(irq)) { - mp_raise_ValueError("irq claimed by external resource"); + mp_raise_ValueError(MP_ERROR_TEXT("irq claimed by external resource")); // If the IRQ is not set, add our handler } else if (!current) { irq_set_exclusive_handler(irq, rp2_pio_get_irq_handler(pio)); @@ -304,7 +304,7 @@ static mp_obj_t rp2_pio_make_new(const mp_obj_type_t *type, size_t n_args, size_ // Get the PIO object. int pio_id = mp_obj_get_int(args[0]); if (!(0 <= pio_id && pio_id < MP_ARRAY_SIZE(rp2_pio_obj))) { - mp_raise_ValueError("invalid PIO"); + mp_raise_ValueError(MP_ERROR_TEXT("invalid PIO")); } const rp2_pio_obj_t *self = &rp2_pio_obj[pio_id]; @@ -353,7 +353,7 @@ static mp_obj_t rp2_pio_remove_program(size_t n_args, const mp_obj_t *args) { length = bufinfo.len / 2; offset = mp_obj_get_int(prog[PROG_OFFSET_PIO0 + pio_get_index(self->pio)]); if (offset < 0) { - mp_raise_ValueError("prog not in instruction memory"); + mp_raise_ValueError(MP_ERROR_TEXT("prog not in instruction memory")); } // Invalidate the program offset in the program object. prog[PROG_OFFSET_PIO0 + pio_get_index(self->pio)] = MP_OBJ_NEW_SMALL_INT(-1); @@ -374,7 +374,7 @@ static mp_obj_t rp2_pio_state_machine(size_t n_args, const mp_obj_t *pos_args, m // Get and verify the state machine id. mp_int_t sm_id = mp_obj_get_int(pos_args[1]); if (!(0 <= sm_id && sm_id < 4)) { - mp_raise_ValueError("invalid StateMachine"); + mp_raise_ValueError(MP_ERROR_TEXT("invalid StateMachine")); } // Return the correct StateMachine object. @@ -400,7 +400,7 @@ static mp_obj_t rp2_pio_gpio_base(size_t n_args, const mp_obj_t *args) { // Must be 0 for GPIOs 0 to 31 inclusive, or 16 for GPIOs 16 to 48 inclusive. if (!(gpio_base == 0 || gpio_base == 16)) { - mp_raise_ValueError("invalid GPIO base"); + mp_raise_ValueError(MP_ERROR_TEXT("invalid GPIO base")); } if (pio_set_gpio_base(self->pio, gpio_base) != PICO_OK) { @@ -556,14 +556,14 @@ static const rp2_state_machine_obj_t rp2_state_machine_obj[] = { static const rp2_state_machine_obj_t *rp2_state_machine_get_object(mp_int_t sm_id) { if (!(0 <= sm_id && sm_id < MP_ARRAY_SIZE(rp2_state_machine_obj))) { - mp_raise_ValueError("invalid StateMachine"); + mp_raise_ValueError(MP_ERROR_TEXT("invalid StateMachine")); } const rp2_state_machine_obj_t *sm_obj = &rp2_state_machine_obj[sm_id]; if (!(rp2_state_machine_claimed_mask & (1 << sm_id))) { if (pio_sm_is_claimed(sm_obj->pio, sm_obj->sm)) { - mp_raise_ValueError("StateMachine claimed by external resource"); + mp_raise_ValueError(MP_ERROR_TEXT("StateMachine claimed by external resource")); } pio_sm_claim(sm_obj->pio, sm_obj->sm); rp2_state_machine_claimed_mask |= 1 << sm_id; @@ -830,7 +830,7 @@ static mp_obj_t rp2_state_machine_get(size_t n_args, const mp_obj_t *args) { *(uint32_t *)dest = value; dest += sizeof(uint32_t); } else { - mp_raise_ValueError("unsupported buffer type"); + mp_raise_ValueError(MP_ERROR_TEXT("unsupported buffer type")); } if (dest >= dest_top) { return args[1]; @@ -868,7 +868,7 @@ static mp_obj_t rp2_state_machine_put(size_t n_args, const mp_obj_t *args) { value = *(uint32_t *)src; src += sizeof(uint32_t); } else { - mp_raise_ValueError("unsupported buffer type"); + mp_raise_ValueError(MP_ERROR_TEXT("unsupported buffer type")); } while (pio_sm_is_tx_fifo_full(self->pio, self->sm)) { // This delay must be fast. From 569d472bc7f6f6fb20f69fcdb3ff19f525dec071 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 26 Mar 2025 10:21:52 +1100 Subject: [PATCH 013/154] extmod/modbluetooth: Use newer mp_map_slot_is_filled function. Required in MICROPY_PREVIEW_VERSION_2. Signed-off-by: Andrew Leech --- extmod/modbluetooth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 7049c35dfdf56..b95c42a4ee14a 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -340,7 +340,7 @@ static mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map } for (size_t i = 0; i < kwargs->alloc; ++i) { - if (MP_MAP_SLOT_IS_FILLED(kwargs, i)) { + if (mp_map_slot_is_filled(kwargs, i)) { mp_map_elem_t *e = &kwargs->table[i]; switch (mp_obj_str_get_qstr(e->key)) { case MP_QSTR_gap_name: { From d6c673f28f12978625577a4d1214ebf450d57c8d Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 26 Mar 2025 10:23:55 +1100 Subject: [PATCH 014/154] stm32/main: Replace mp_stack_set calls with new mp_cstack_init_with_top. Required in MICROPY_PREVIEW_VERSION_2. Signed-off-by: Andrew Leech --- ports/stm32/main.c | 7 ++----- ports/stm32/mpconfigport.h | 1 + 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/ports/stm32/main.c b/ports/stm32/main.c index 0f904a8ba958f..e8395013b9188 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -515,11 +515,8 @@ void stm32_main(uint32_t reset_mode) { mp_thread_init(); #endif - // Stack limit should be less than real stack size, so we have a chance - // to recover from limit hit. (Limit is measured in bytes.) - // Note: stack control relies on main thread being initialised above - mp_stack_set_top(&_estack); - mp_stack_set_limit((char *)&_estack - (char *)&_sstack - 1024); + // Stack limit init. + mp_cstack_init_with_top(&_estack, (char *)&_estack - (char *)&_sstack); // GC init gc_init(MICROPY_HEAP_START, MICROPY_HEAP_END); diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index da86fa641623a..d1d6fe249bd6f 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -43,6 +43,7 @@ #define MICROPY_GC_STACK_ENTRY_TYPE uint16_t #endif #endif +#define MICROPY_STACK_CHECK_MARGIN (1024) #define MICROPY_ALLOC_PATH_MAX (128) // optimisations From bb1489965f74cfe839c3452e1b6a04e82c695bf5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 13 Mar 2025 15:18:48 +1100 Subject: [PATCH 015/154] py/mkrules.cmake: Add CMake support for compressed error messages. Signed-off-by: Damien George --- py/mkrules.cmake | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/py/mkrules.cmake b/py/mkrules.cmake index 4374b8b4da3cb..27d8b24f67ac8 100644 --- a/py/mkrules.cmake +++ b/py/mkrules.cmake @@ -14,6 +14,9 @@ set(MICROPY_MODULEDEFS "${MICROPY_GENHDR_DIR}/moduledefs.h") set(MICROPY_ROOT_POINTERS_SPLIT "${MICROPY_GENHDR_DIR}/root_pointers.split") set(MICROPY_ROOT_POINTERS_COLLECTED "${MICROPY_GENHDR_DIR}/root_pointers.collected") set(MICROPY_ROOT_POINTERS "${MICROPY_GENHDR_DIR}/root_pointers.h") +set(MICROPY_COMPRESSED_SPLIT "${MICROPY_GENHDR_DIR}/compressed.split") +set(MICROPY_COMPRESSED_COLLECTED "${MICROPY_GENHDR_DIR}/compressed.collected") +set(MICROPY_COMPRESSED_DATA "${MICROPY_GENHDR_DIR}/compressed.data.h") if(NOT MICROPY_PREVIEW_VERSION_2) set(MICROPY_PREVIEW_VERSION_2 0) @@ -32,6 +35,13 @@ if(MICROPY_BOARD) ) endif() +# Need to do this before extracting MICROPY_CPP_DEF below. +if(MICROPY_ROM_TEXT_COMPRESSION) + target_compile_definitions(${MICROPY_TARGET} PUBLIC + MICROPY_ROM_TEXT_COMPRESSION=\(1\) + ) +endif() + # Need to do this before extracting MICROPY_CPP_DEF below. Rest of frozen # manifest handling is at the end of this file. if(MICROPY_FROZEN_MANIFEST) @@ -84,6 +94,12 @@ target_sources(${MICROPY_TARGET} PRIVATE ${MICROPY_ROOT_POINTERS} ) +if(MICROPY_ROM_TEXT_COMPRESSION) + target_sources(${MICROPY_TARGET} PRIVATE + ${MICROPY_COMPRESSED_DATA} + ) +endif() + # Command to force the build of another command # Generate mpversion.h @@ -197,6 +213,32 @@ add_custom_command( DEPENDS ${MICROPY_ROOT_POINTERS_COLLECTED} ${MICROPY_PY_DIR}/make_root_pointers.py ) +# Generate compressed.data.h + +add_custom_command( + OUTPUT ${MICROPY_COMPRESSED_SPLIT} + COMMAND ${Python3_EXECUTABLE} ${MICROPY_PY_DIR}/makeqstrdefs.py split compress ${MICROPY_QSTRDEFS_LAST} ${MICROPY_GENHDR_DIR}/compress _ + COMMAND touch ${MICROPY_COMPRESSED_SPLIT} + DEPENDS ${MICROPY_QSTRDEFS_LAST} + VERBATIM + COMMAND_EXPAND_LISTS +) + +add_custom_command( + OUTPUT ${MICROPY_COMPRESSED_COLLECTED} + COMMAND ${Python3_EXECUTABLE} ${MICROPY_PY_DIR}/makeqstrdefs.py cat compress _ ${MICROPY_GENHDR_DIR}/compress ${MICROPY_COMPRESSED_COLLECTED} + BYPRODUCTS "${MICROPY_COMPRESSED_COLLECTED}.hash" + DEPENDS ${MICROPY_COMPRESSED_SPLIT} + VERBATIM + COMMAND_EXPAND_LISTS +) + +add_custom_command( + OUTPUT ${MICROPY_COMPRESSED_DATA} + COMMAND ${Python3_EXECUTABLE} ${MICROPY_PY_DIR}/makecompresseddata.py ${MICROPY_COMPRESSED_COLLECTED} > ${MICROPY_COMPRESSED_DATA} + DEPENDS ${MICROPY_COMPRESSED_COLLECTED} ${MICROPY_PY_DIR}/makecompresseddata.py +) + # Build frozen code if enabled if(MICROPY_FROZEN_MANIFEST) From bfe16ef09bd06acf359a214f97a1b21f5fc71d66 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 13 Mar 2025 15:19:08 +1100 Subject: [PATCH 016/154] esp32: Enable compressed error messages by default. Reduces firmware size by about 3300 bytes. Signed-off-by: Damien George --- ports/esp32/esp32_common.cmake | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index dedef6d782cae..a9827249bd334 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -28,6 +28,11 @@ if(NOT DEFINED MICROPY_PY_TINYUSB) endif() endif() +# Enable error text compression by default. +if(NOT MICROPY_ROM_TEXT_COMPRESSION) + set(MICROPY_ROM_TEXT_COMPRESSION ON) +endif() + # Include core source components. include(${MICROPY_DIR}/py/py.cmake) From 048ccccee01cdb1f36242d4ee5bba43a6900c891 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 13 Mar 2025 15:19:24 +1100 Subject: [PATCH 017/154] rp2: Enable compressed error messages by default. Reduces firmware size by about 3000 bytes. Signed-off-by: Damien George --- ports/rp2/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 1a5029c150417..fcc1a1e1b9b96 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -66,6 +66,11 @@ if(NOT MICROPY_C_HEAP_SIZE) set(MICROPY_C_HEAP_SIZE 0) endif() +# Enable error text compression by default. +if(NOT MICROPY_ROM_TEXT_COMPRESSION) + set(MICROPY_ROM_TEXT_COMPRESSION ON) +endif() + # Enable extmod components that will be configured by extmod.cmake. # A board may also have enabled additional components. set(MICROPY_SSL_MBEDTLS ON) From e3c2cf7a040052143b19ef140fd666f86ec0fbda Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 21 Mar 2025 02:12:31 +0100 Subject: [PATCH 018/154] esp32/esp32_common.cmake: Skip BTree module when requested. This commit makes the BTree module truly optional, as it was unconditionally enabled in the shared CMake script for the port. This meant that if a board/variant did explicitly turn BTree off said request was not honoured by the build system and the BTree module would still be brought in. Signed-off-by: Alessandro Gatti --- ports/esp32/esp32_common.cmake | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index a9827249bd334..c54f5a5403226 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -42,7 +42,9 @@ include(${MICROPY_DIR}/py/py.cmake) if(NOT CMAKE_BUILD_EARLY_EXPANSION) # Enable extmod components that will be configured by extmod.cmake. # A board may also have enabled additional components. - set(MICROPY_PY_BTREE ON) + if (NOT DEFINED MICROPY_PY_BTREE) + set(MICROPY_PY_BTREE ON) + endif() include(${MICROPY_DIR}/py/usermod.cmake) include(${MICROPY_DIR}/extmod/extmod.cmake) @@ -276,7 +278,9 @@ target_include_directories(${MICROPY_TARGET} PUBLIC ) # Add additional extmod and usermod components. -target_link_libraries(${MICROPY_TARGET} micropy_extmod_btree) +if (MICROPY_PY_BTREE) + target_link_libraries(${MICROPY_TARGET} micropy_extmod_btree) +endif() target_link_libraries(${MICROPY_TARGET} usermod) # Extra linker options From 193460d18f018e7306812fb41283fc241342eb1a Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Fri, 28 Feb 2025 09:48:52 +0100 Subject: [PATCH 019/154] drivers/esp-hosted: Rename Bluetooth HCI backend driver. Rename `bthci` to `bthci_uart` for consistency with the CYW43 driver and to distinguish it from HCI backends that use a different transport. Signed-off-by: iabdalkader --- .../esp-hosted/{esp_hosted_bthci.c => esp_hosted_bthci_uart.c} | 0 extmod/extmod.mk | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename drivers/esp-hosted/{esp_hosted_bthci.c => esp_hosted_bthci_uart.c} (100%) diff --git a/drivers/esp-hosted/esp_hosted_bthci.c b/drivers/esp-hosted/esp_hosted_bthci_uart.c similarity index 100% rename from drivers/esp-hosted/esp_hosted_bthci.c rename to drivers/esp-hosted/esp_hosted_bthci_uart.c diff --git a/extmod/extmod.mk b/extmod/extmod.mk index 859f610c90ac1..b2a0f490b6e10 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -504,7 +504,7 @@ ESP_HOSTED_SRC_C = $(addprefix $(ESP_HOSTED_DIR)/,\ ) ifeq ($(MICROPY_PY_BLUETOOTH),1) -ESP_HOSTED_SRC_C += $(ESP_HOSTED_DIR)/esp_hosted_bthci.c +ESP_HOSTED_SRC_C += $(ESP_HOSTED_DIR)/esp_hosted_bthci_uart.c endif # Include the protobuf-c support functions From fa76d52edbbecf4b3dbc27238d4ec83fb88a55ff Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Fri, 28 Feb 2025 09:52:19 +0100 Subject: [PATCH 020/154] drivers/ninaw10: Rename Bluetooth HCI backend driver. Rename `bt_hci` to `bthci_uart` for consistency with the CYW43 driver and to distinguish it from HCI backends that use a different transport. Signed-off-by: iabdalkader --- drivers/ninaw10/{nina_bt_hci.c => nina_bthci_uart.c} | 4 ++-- ports/rp2/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename drivers/ninaw10/{nina_bt_hci.c => nina_bthci_uart.c} (98%) diff --git a/drivers/ninaw10/nina_bt_hci.c b/drivers/ninaw10/nina_bthci_uart.c similarity index 98% rename from drivers/ninaw10/nina_bt_hci.c rename to drivers/ninaw10/nina_bthci_uart.c index f0d1b9bc8986b..50631711097f2 100644 --- a/drivers/ninaw10/nina_bt_hci.c +++ b/drivers/ninaw10/nina_bthci_uart.c @@ -50,8 +50,8 @@ #define OCF_SET_EVENT_MASK (0x0001) #define OCF_RESET (0x0003) -#define error_printf(...) // mp_printf(&mp_plat_print, "nina_bt_hci.c: " __VA_ARGS__) -#define debug_printf(...) // mp_printf(&mp_plat_print, "nina_bt_hci.c: " __VA_ARGS__) +#define error_printf(...) // mp_printf(&mp_plat_print, "nina_bthci_uart.c: " __VA_ARGS__) +#define debug_printf(...) // mp_printf(&mp_plat_print, "nina_bthci_uart.c: " __VA_ARGS__) // Provided by the port, and also possibly shared with the stack. extern uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index fcc1a1e1b9b96..53d00c7dfdaa4 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -431,7 +431,7 @@ if (MICROPY_PY_NETWORK_NINAW10) # Enable NINA-W10 WiFi and Bluetooth drivers. list(APPEND MICROPY_SOURCE_DRIVERS - ${MICROPY_DIR}/drivers/ninaw10/nina_bt_hci.c + ${MICROPY_DIR}/drivers/ninaw10/nina_bthci_uart.c ${MICROPY_DIR}/drivers/ninaw10/nina_wifi_drv.c ${MICROPY_DIR}/drivers/ninaw10/nina_wifi_bsp.c ${MICROPY_DIR}/drivers/ninaw10/machine_pin_nina.c From 399c10dc28eba4ddd163dd0e930529f1b81d514a Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 3 Mar 2025 17:32:41 +0100 Subject: [PATCH 021/154] mimxrt/machine_uart: Enable CTS SION so it can be read. The new CYW43 BTHCI UART backend requires CTS pin to be defined and readable. This patch enables the CTS pin SION bit to allow it to be read regardless of mux mode. Signed-off-by: iabdalkader --- ports/mimxrt/machine_uart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/mimxrt/machine_uart.c b/ports/mimxrt/machine_uart.c index 4dcaf72f9862a..107af72297d4f 100644 --- a/ports/mimxrt/machine_uart.c +++ b/ports/mimxrt/machine_uart.c @@ -136,7 +136,7 @@ bool lpuart_set_iomux_cts(int8_t uart) { int index = (uart - 1) * 2; if (CTS.muxRegister != 0) { - IOMUXC_SetPinMux(CTS.muxRegister, CTS.muxMode, CTS.inputRegister, CTS.inputDaisy, CTS.configRegister, 0U); + IOMUXC_SetPinMux(CTS.muxRegister, CTS.muxMode, CTS.inputRegister, CTS.inputDaisy, CTS.configRegister, 1U); IOMUXC_SetPinConfig(CTS.muxRegister, CTS.muxMode, CTS.inputRegister, CTS.inputDaisy, CTS.configRegister, pin_generate_config(PIN_PULL_UP_100K, PIN_MODE_IN, PIN_DRIVE_6, CTS.configRegister)); return true; From 3bbed952fdc884d85112a15715152c9c4f558c14 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 3 Mar 2025 17:46:56 +0100 Subject: [PATCH 022/154] mimxrt/cyw43_configport: Update cyw43 config to use new BTHCI UART. Update the cyw43 configuration to use the new BTHCI UART backend provided by cyw43-driver. Signed-off-by: iabdalkader --- ports/mimxrt/Makefile | 1 - ports/mimxrt/cyw43_configport.h | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index c0358387f975f..7788b54ca57ff 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -280,7 +280,6 @@ endif ifeq ($(MICROPY_PY_BLUETOOTH),1) SRC_C += mpbthciport.c -DRIVERS_SRC_C += drivers/cyw43/cywbt.c endif # MICROPY_PY_BLUETOOTH ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) diff --git a/ports/mimxrt/cyw43_configport.h b/ports/mimxrt/cyw43_configport.h index ab535a477763d..cf2f5d4b9fb55 100644 --- a/ports/mimxrt/cyw43_configport.h +++ b/ports/mimxrt/cyw43_configport.h @@ -32,10 +32,12 @@ #include "py/mperrno.h" #include "py/mphal.h" #include "extmod/modnetwork.h" +#include "extmod/mpbthci.h" #include "pendsv.h" #include "sdio.h" #define CYW43_USE_SPI (0) +#define CYW43_ENABLE_BLUETOOTH_OVER_UART (1) #define CYW43_LWIP (1) #define CYW43_USE_STATS (0) @@ -47,6 +49,18 @@ #define CYW43_WIFI_NVRAM_INCLUDE_FILE "lib/cyw43-driver/firmware/wifi_nvram_1dx.h" #endif +#ifndef CYW43_BT_FIRMWARE_INCLUDE_FILE +#define CYW43_BT_FIRMWARE_INCLUDE_FILE "lib/cyw43-driver/firmware/cyw43_btfw_4343A1.h" +#endif + +#ifdef MICROPY_HW_BLE_UART_BAUDRATE_SECONDARY +#define CYW43_BT_UART_BAUDRATE_ACTIVE_USE MICROPY_HW_BLE_UART_BAUDRATE_SECONDARY +#endif + +#ifdef MICROPY_HW_BLE_UART_BAUDRATE_DOWNLOAD_FIRMWARE +#define CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE MICROPY_HW_BLE_UART_BAUDRATE_DOWNLOAD_FIRMWARE +#endif + #define CYW43_IOCTL_TIMEOUT_US (1000000) #define CYW43_SLEEP_MAX (50) #define CYW43_NETUTILS (1) @@ -75,6 +89,7 @@ #define CYW43_HAL_PIN_PULL_DOWN MP_HAL_PIN_PULL_DOWN #define CYW43_HAL_MAC_WLAN0 MP_HAL_MAC_WLAN0 +#define CYW43_HAL_MAC_BDADDR MP_HAL_MAC_BDADDR #define cyw43_hal_ticks_us mp_hal_ticks_us #define cyw43_hal_ticks_ms mp_hal_ticks_ms @@ -88,9 +103,19 @@ #define cyw43_hal_get_mac_ascii mp_hal_get_mac_ascii #define cyw43_hal_generate_laa_mac mp_hal_generate_laa_mac +#define cyw43_hal_uart_set_baudrate mp_bluetooth_hci_uart_set_baudrate +#define cyw43_hal_uart_write mp_bluetooth_hci_uart_write +#define cyw43_hal_uart_readchar mp_bluetooth_hci_uart_readchar + #define cyw43_delay_us mp_hal_delay_us #define cyw43_delay_ms mp_hal_delay_ms +#define cyw43_bluetooth_controller_init mp_bluetooth_hci_controller_init +#define cyw43_bluetooth_controller_deinit mp_bluetooth_hci_controller_deinit +#define cyw43_bluetooth_controller_woken mp_bluetooth_hci_controller_woken +#define cyw43_bluetooth_controller_wakeup mp_bluetooth_hci_controller_wakeup +#define cyw43_bluetooth_controller_sleep_maybe mp_bluetooth_hci_controller_sleep_maybe + #define CYW43_PIN_WL_REG_ON MICROPY_HW_WL_REG_ON #define CYW43_PIN_WL_HOST_WAKE MICROPY_HW_WL_HOST_WAKE #define CYW43_PIN_WL_SDIO_1 MICROPY_HW_SDIO_D1 From 0f360880aa01ca87c637eaa3517a90a78c2dbfd2 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sun, 30 Mar 2025 09:29:57 +0200 Subject: [PATCH 023/154] stm32/cyw43_configport: Update cyw43 config to use new BTHCI UART. Update the cyw43 configuration to use the new BTHCI UART backend provided by cyw43-driver. Signed-off-by: iabdalkader --- ports/stm32/Makefile | 1 - ports/stm32/boards/PYBD_SF2/f722_qspi.ld | 2 +- ports/stm32/cyw43_configport.h | 27 ++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index c938dcbda7d6d..8ac9a8af03d1a 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -432,7 +432,6 @@ endif ifeq ($(MICROPY_PY_BLUETOOTH),1) SRC_C += mpbthciport.c -DRIVERS_SRC_C += drivers/cyw43/cywbt.c ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) SRC_C += mpnimbleport.c diff --git a/ports/stm32/boards/PYBD_SF2/f722_qspi.ld b/ports/stm32/boards/PYBD_SF2/f722_qspi.ld index f5e6769834a09..354c1919b41a3 100644 --- a/ports/stm32/boards/PYBD_SF2/f722_qspi.ld +++ b/ports/stm32/boards/PYBD_SF2/f722_qspi.ld @@ -54,7 +54,7 @@ SECTIONS *lib/mbedtls/*(.text* .rodata*) *lib/mynewt-nimble/*(.text* .rodata*) *lib/cyw43-driver/*(.rodata.w4343*_combined) - *drivers/cyw43/*(.rodata.cyw43_btfw_*) + *lib/cyw43-driver/*(.rodata.cyw43_btfw_*) . = ALIGN(4); } >FLASH_EXT } diff --git a/ports/stm32/cyw43_configport.h b/ports/stm32/cyw43_configport.h index 595051180336e..c26c55476167c 100644 --- a/ports/stm32/cyw43_configport.h +++ b/ports/stm32/cyw43_configport.h @@ -32,11 +32,13 @@ #include "py/mperrno.h" #include "py/mphal.h" #include "extmod/modnetwork.h" +#include "extmod/mpbthci.h" #include "extint.h" #include "pendsv.h" #include "sdio.h" #define CYW43_USE_SPI (0) +#define CYW43_ENABLE_BLUETOOTH_OVER_UART (1) #define CYW43_LWIP (1) #define CYW43_USE_STATS (0) @@ -48,6 +50,18 @@ #define CYW43_WIFI_NVRAM_INCLUDE_FILE "lib/cyw43-driver/firmware/wifi_nvram_1dx.h" #endif +#ifndef CYW43_BT_FIRMWARE_INCLUDE_FILE +#define CYW43_BT_FIRMWARE_INCLUDE_FILE "lib/cyw43-driver/firmware/cyw43_btfw_4343A1.h" +#endif + +#ifdef MICROPY_HW_BLE_UART_BAUDRATE_SECONDARY +#define CYW43_BT_UART_BAUDRATE_ACTIVE_USE MICROPY_HW_BLE_UART_BAUDRATE_SECONDARY +#endif + +#ifdef MICROPY_HW_BLE_UART_BAUDRATE_DOWNLOAD_FIRMWARE +#define CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE MICROPY_HW_BLE_UART_BAUDRATE_DOWNLOAD_FIRMWARE +#endif + #define CYW43_IOCTL_TIMEOUT_US (1000000) #define CYW43_SLEEP_MAX (50) #define CYW43_NETUTILS (1) @@ -66,6 +80,7 @@ #define CYW43_SDPCM_SEND_COMMON_WAIT __WFI(); #define CYW43_DO_IOCTL_WAIT __WFI(); +#define CYW43_HAL_UART_READCHAR_BLOCKING_WAIT __WFI() #define CYW43_ARRAY_SIZE(a) MP_ARRAY_SIZE(a) @@ -76,6 +91,7 @@ #define CYW43_HAL_PIN_PULL_DOWN MP_HAL_PIN_PULL_DOWN #define CYW43_HAL_MAC_WLAN0 MP_HAL_MAC_WLAN0 +#define CYW43_HAL_MAC_BDADDR MP_HAL_MAC_BDADDR #define cyw43_hal_ticks_us mp_hal_ticks_us #define cyw43_hal_ticks_ms mp_hal_ticks_ms @@ -90,6 +106,16 @@ #define cyw43_hal_get_mac_ascii mp_hal_get_mac_ascii #define cyw43_hal_generate_laa_mac mp_hal_generate_laa_mac +#define cyw43_hal_uart_set_baudrate mp_bluetooth_hci_uart_set_baudrate +#define cyw43_hal_uart_write mp_bluetooth_hci_uart_write +#define cyw43_hal_uart_readchar mp_bluetooth_hci_uart_readchar + +#define cyw43_bluetooth_controller_init mp_bluetooth_hci_controller_init +#define cyw43_bluetooth_controller_deinit mp_bluetooth_hci_controller_deinit +#define cyw43_bluetooth_controller_woken mp_bluetooth_hci_controller_woken +#define cyw43_bluetooth_controller_wakeup mp_bluetooth_hci_controller_wakeup +#define cyw43_bluetooth_controller_sleep_maybe mp_bluetooth_hci_controller_sleep_maybe + #define CYW43_PIN_WL_REG_ON pyb_pin_WL_REG_ON #define CYW43_PIN_WL_HOST_WAKE pyb_pin_WL_HOST_WAKE #define CYW43_PIN_WL_SDIO_1 pyb_pin_WL_SDIO_1 @@ -103,6 +129,7 @@ #if MICROPY_HW_ENABLE_RF_SWITCH #define CYW43_PIN_RFSW_VDD pyb_pin_WL_RFSW_VDD +#define CYW43_PIN_RFSW_SELECT pyb_pin_WL_GPIO_1 #endif #define cyw43_schedule_internal_poll_dispatch(func) pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, func) From 0b729623366dab3aa2575eeab934b2629f8e52c5 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sun, 30 Mar 2025 09:31:26 +0200 Subject: [PATCH 024/154] drivers/cyw43: Remove old BTHCI UART backend. It has been completely replaced by equivalent code in cyw43-driver. Signed-off-by: iabdalkader --- drivers/cyw43/README.md | 17 --- drivers/cyw43/cywbt.c | 304 ---------------------------------------- 2 files changed, 321 deletions(-) delete mode 100644 drivers/cyw43/README.md delete mode 100644 drivers/cyw43/cywbt.c diff --git a/drivers/cyw43/README.md b/drivers/cyw43/README.md deleted file mode 100644 index 5af6f65580668..0000000000000 --- a/drivers/cyw43/README.md +++ /dev/null @@ -1,17 +0,0 @@ -CYW43xx WiFi SoC driver -======================= - -This is a driver for the CYW43xx WiFi SoC. - -There are four layers to the driver: - -1. SDIO bus interface, provided by the host device/system. - -2. Low-level CYW43xx interface, managing the bus, control messages, Ethernet - frames and asynchronous events. Includes download of SoC firmware. The - header file `cyw43_ll.h` defines the interface to this layer. - -3. Mid-level CYW43xx control, to control and set WiFi parameters and manage - events. See `cyw43_ctrl.c`. - -4. TCP/IP bindings to lwIP. See `cyw43_lwip.c`. diff --git a/drivers/cyw43/cywbt.c b/drivers/cyw43/cywbt.c deleted file mode 100644 index 0ee69a07ecd2c..0000000000000 --- a/drivers/cyw43/cywbt.c +++ /dev/null @@ -1,304 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2019-2020 Damien P. George - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include -#include - -#include "py/runtime.h" -#include "py/mphal.h" -#include "extmod/mpbthci.h" - -#if MICROPY_PY_NETWORK_CYW43 - -#include "lib/cyw43-driver/src/cyw43_config.h" -#include "lib/cyw43-driver/firmware/cyw43_btfw_4343A1.h" - -// Provided by the port, and also possibly shared with the stack. -extern uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; - -/******************************************************************************/ -// CYW BT HCI low-level driver - -#ifdef CYW43_PIN_BT_CTS -// This code is not portable and currently only builds on stm32 port. - -#include "pin_static_af.h" -#include "uart.h" - -// Provided by the port. -extern machine_uart_obj_t mp_bluetooth_hci_uart_obj; - -static void cywbt_wait_cts_low(void) { - mp_hal_pin_config(CYW43_PIN_BT_CTS, MP_HAL_PIN_MODE_INPUT, MP_HAL_PIN_PULL_UP, 0); - for (int i = 0; i < 200; ++i) { - if (mp_hal_pin_read(CYW43_PIN_BT_CTS) == 0) { - break; - } - mp_hal_delay_ms(1); - } - mp_hal_pin_config_alt(CYW43_PIN_BT_CTS, MP_HAL_PIN_MODE_ALT, - MP_HAL_PIN_PULL_UP, AF_FN_UART, mp_bluetooth_hci_uart_obj.uart_id); -} -#endif - -static int cywbt_hci_cmd_raw(size_t len, uint8_t *buf) { - mp_bluetooth_hci_uart_write((void *)buf, len); - for (int c, i = 0; i < 6; ++i) { - while ((c = mp_bluetooth_hci_uart_readchar()) == -1) { - mp_event_wait_indefinite(); - } - buf[i] = c; - } - - // expect a command complete event (event 0x0e) - if (buf[0] != 0x04 || buf[1] != 0x0e) { - printf("unknown response: %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3]); - return -1; - } - - /* - if buf[3:6] != cmd[:3]: - print('response doesn\'t match cmd:', cmd, ev) - return b'' - */ - - int sz = buf[2] - 3; - for (int c, i = 0; i < sz; ++i) { - while ((c = mp_bluetooth_hci_uart_readchar()) == -1) { - mp_event_wait_indefinite(); - } - buf[i] = c; - } - - return 0; -} - -static int cywbt_hci_cmd(int ogf, int ocf, size_t param_len, const uint8_t *param_buf) { - uint8_t *buf = mp_bluetooth_hci_cmd_buf; - buf[0] = 0x01; - buf[1] = ocf; - buf[2] = ogf << 2 | ocf >> 8; - buf[3] = param_len; - if (param_len) { - memcpy(buf + 4, param_buf, param_len); - } - return cywbt_hci_cmd_raw(4 + param_len, buf); -} - -static void put_le16(uint8_t *buf, uint16_t val) { - buf[0] = val; - buf[1] = val >> 8; -} - -static void put_le32(uint8_t *buf, uint32_t val) { - buf[0] = val; - buf[1] = val >> 8; - buf[2] = val >> 16; - buf[3] = val >> 24; -} - -static int cywbt_set_baudrate(uint32_t baudrate) { - uint8_t buf[6]; - put_le16(buf, 0); - put_le32(buf + 2, baudrate); - return cywbt_hci_cmd(0x3f, 0x18, 6, buf); -} - -// download firmware -static int cywbt_download_firmware(const uint8_t *firmware) { - cywbt_hci_cmd(0x3f, 0x2e, 0, NULL); - - bool last_packet = false; - while (!last_packet) { - uint8_t *buf = mp_bluetooth_hci_cmd_buf; - memcpy(buf + 1, firmware, 3); - firmware += 3; - last_packet = buf[1] == 0x4e; - if (buf[2] != 0xfc) { - printf("fail1 %02x\n", buf[2]); - break; - } - uint8_t len = buf[3]; - - memcpy(buf + 4, firmware, len); - firmware += len; - - buf[0] = 1; - cywbt_hci_cmd_raw(4 + len, buf); - if (buf[0] != 0) { - printf("fail3 %02x\n", buf[0]); - break; - } - } - - // RF switch must select high path during BT patch boot - #if MICROPY_HW_ENABLE_RF_SWITCH - mp_hal_pin_config(CYW43_PIN_WL_GPIO_1, MP_HAL_PIN_MODE_INPUT, MP_HAL_PIN_PULL_UP, 0); - #endif - mp_hal_delay_ms(10); // give some time for CTS to go high - #ifdef CYW43_PIN_BT_CTS - cywbt_wait_cts_low(); - #endif - #if MICROPY_HW_ENABLE_RF_SWITCH - // Select chip antenna (could also select external) - mp_hal_pin_config(CYW43_PIN_WL_GPIO_1, MP_HAL_PIN_MODE_INPUT, MP_HAL_PIN_PULL_DOWN, 0); - #endif - - mp_bluetooth_hci_uart_set_baudrate(115200); - cywbt_set_baudrate(MICROPY_HW_BLE_UART_BAUDRATE_SECONDARY); - mp_bluetooth_hci_uart_set_baudrate(MICROPY_HW_BLE_UART_BAUDRATE_SECONDARY); - - return 0; -} - -int mp_bluetooth_hci_controller_init(void) { - // This is called immediately after the UART is initialised during stack initialisation. - - mp_hal_pin_output(CYW43_PIN_BT_REG_ON); - mp_hal_pin_low(CYW43_PIN_BT_REG_ON); - #ifdef CYW43_PIN_BT_HOST_WAKE - mp_hal_pin_input(CYW43_PIN_BT_HOST_WAKE); - #endif - #ifdef CYW43_PIN_BT_DEV_WAKE - mp_hal_pin_output(CYW43_PIN_BT_DEV_WAKE); - mp_hal_pin_low(CYW43_PIN_BT_DEV_WAKE); - #endif - - #if MICROPY_HW_ENABLE_RF_SWITCH - // TODO don't select antenna if wifi is enabled - mp_hal_pin_config(CYW43_PIN_WL_GPIO_4, MP_HAL_PIN_MODE_OUTPUT, MP_HAL_PIN_PULL_NONE, 0); // RF-switch power - mp_hal_pin_high(CYW43_PIN_WL_GPIO_4); // Turn the RF-switch on - #endif - - uint8_t buf[256]; - - mp_hal_pin_low(CYW43_PIN_BT_REG_ON); - mp_bluetooth_hci_uart_set_baudrate(115200); - mp_hal_delay_ms(100); - mp_hal_pin_high(CYW43_PIN_BT_REG_ON); - #ifdef CYW43_PIN_BT_CTS - cywbt_wait_cts_low(); - #else - mp_hal_delay_ms(100); - #endif - - // Reset - cywbt_hci_cmd(0x03, 0x0003, 0, NULL); - - #ifdef MICROPY_HW_BLE_UART_BAUDRATE_DOWNLOAD_FIRMWARE - // Change baudrate - cywbt_set_baudrate(MICROPY_HW_BLE_UART_BAUDRATE_DOWNLOAD_FIRMWARE); - mp_bluetooth_hci_uart_set_baudrate(MICROPY_HW_BLE_UART_BAUDRATE_DOWNLOAD_FIRMWARE); - #endif - - cywbt_download_firmware((const uint8_t *)&cyw43_btfw_4343A1[0]); - - // Reset - cywbt_hci_cmd(0x03, 0x0003, 0, NULL); - - // Set BD_ADDR (sent as little endian) - uint8_t bdaddr[6]; - mp_hal_get_mac(MP_HAL_MAC_BDADDR, bdaddr); - buf[0] = bdaddr[5]; - buf[1] = bdaddr[4]; - buf[2] = bdaddr[3]; - buf[3] = bdaddr[2]; - buf[4] = bdaddr[1]; - buf[5] = bdaddr[0]; - cywbt_hci_cmd(0x3f, 0x0001, 6, buf); - - // Set local name - // memset(buf, 0, 248); - // memcpy(buf, "PYBD-BLE", 8); - // cywbt_hci_cmd(0x03, 0x0013, 248, buf); - - // Configure sleep mode - cywbt_hci_cmd(0x3f, 0x27, 12, (const uint8_t *)"\x01\x02\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00"); - - // HCI_Write_LE_Host_Support - cywbt_hci_cmd(3, 109, 2, (const uint8_t *)"\x01\x00"); - - #ifdef CYW43_PIN_BT_DEV_WAKE - mp_hal_pin_high(CYW43_PIN_BT_DEV_WAKE); // let sleep - #endif - - return 0; -} - -int mp_bluetooth_hci_controller_deinit(void) { - mp_hal_pin_low(CYW43_PIN_BT_REG_ON); - - return 0; -} - -#ifdef CYW43_PIN_BT_DEV_WAKE -static uint32_t bt_sleep_ticks; -#endif - -int mp_bluetooth_hci_controller_sleep_maybe(void) { - #ifdef CYW43_PIN_BT_DEV_WAKE - if (mp_hal_pin_read(CYW43_PIN_BT_DEV_WAKE) == 0) { - if (mp_hal_ticks_ms() - bt_sleep_ticks > 500) { - mp_hal_pin_high(CYW43_PIN_BT_DEV_WAKE); // let sleep - } - } - #endif - return 0; -} - -bool mp_bluetooth_hci_controller_woken(void) { - #ifdef CYW43_PIN_BT_HOST_WAKE - bool host_wake = mp_hal_pin_read(CYW43_PIN_BT_HOST_WAKE); - /* - // this is just for info/tracing purposes - static bool last_host_wake = false; - if (host_wake != last_host_wake) { - printf("HOST_WAKE change %d -> %d\n", last_host_wake, host_wake); - last_host_wake = host_wake; - } - */ - return host_wake; - #else - return true; - #endif -} - -int mp_bluetooth_hci_controller_wakeup(void) { - #ifdef CYW43_PIN_BT_DEV_WAKE - bt_sleep_ticks = mp_hal_ticks_ms(); - - if (mp_hal_pin_read(CYW43_PIN_BT_DEV_WAKE) == 1) { - mp_hal_pin_low(CYW43_PIN_BT_DEV_WAKE); // wake up - // Use delay_us rather than delay_ms to prevent running the scheduler (which - // might result in more BLE operations). - mp_hal_delay_us(5000); // can't go lower than this - } - #endif - - return 0; -} - -#endif From f5cb9eb9746bea3dc49cc1ae0d424058fb7a09f2 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 24 Apr 2025 10:43:38 +1000 Subject: [PATCH 025/154] top: Bump Ruff version to v0.11.6. Brings it into sync with a matching change to micropython-lib (which was much older). Includes one small automatic fix. Signed-off-by: Angus Gratton --- .github/workflows/ruff.yml | 4 ++-- .pre-commit-config.yaml | 4 ++-- tools/mpy_ld.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 9265c25bcba13..4c4a2a3162ed6 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - # ruff version should be kept in sync with .pre-commit-config.yaml - - run: pipx install ruff==0.9.6 + # ruff version should be kept in sync with .pre-commit-config.yaml & also micropython-lib + - run: pipx install ruff==0.11.6 - run: ruff check --output-format=github . - run: ruff format --diff . diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d07f9b0fda2a9..ac9785bb59232 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,8 +12,8 @@ repos: verbose: true stages: [commit-msg] - repo: https://github.com/charliermarsh/ruff-pre-commit - # Version should be kept in sync with .github/workflows/ruff.yml - rev: v0.9.6 + # Version should be kept in sync with .github/workflows/ruff.yml & also micropython-lib + rev: v0.11.6 hooks: - id: ruff - id: ruff-format diff --git a/tools/mpy_ld.py b/tools/mpy_ld.py index 70cab2b894ba5..a47653f900cc8 100755 --- a/tools/mpy_ld.py +++ b/tools/mpy_ld.py @@ -1131,7 +1131,7 @@ def load_object_file(env, f, felf): elif sym.entry["st_shndx"] == "SHN_UNDEF" and sym["st_info"]["bind"] == "STB_GLOBAL": # Undefined global symbol, needs resolving env.unresolved_syms.append(sym) - if len(dup_errors): + if dup_errors: raise LinkError("\n".join(dup_errors)) @@ -1214,7 +1214,7 @@ def link_objects(env, native_qstr_vals_len): else: undef_errors.append("{}: undefined symbol: {}".format(sym.filename, sym.name)) - if len(undef_errors): + if undef_errors: raise LinkError("\n".join(undef_errors)) # Align sections, assign their addresses, and create full_text From c83e907d9dba3d6be2305b2e2e6b646dd51c495a Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 24 Apr 2025 12:20:19 +1000 Subject: [PATCH 026/154] tests/extmod: Skip binascii tests when hexlify/unhexlify don't exist. These functions are only available when `MICROPY_PY_BUILTINS_BYTES_HEX` is enabled. Signed-off-by: Damien George --- tests/extmod/binascii_hexlify.py | 8 ++++---- tests/extmod/binascii_unhexlify.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/extmod/binascii_hexlify.py b/tests/extmod/binascii_hexlify.py index d06029aabaffb..ae90b67336586 100644 --- a/tests/extmod/binascii_hexlify.py +++ b/tests/extmod/binascii_hexlify.py @@ -1,5 +1,5 @@ try: - import binascii + from binascii import hexlify except ImportError: print("SKIP") raise SystemExit @@ -10,10 +10,10 @@ b"\x7f\x80\xff", b"1234ABCDabcd", ): - print(binascii.hexlify(x)) + print(hexlify(x)) # Two-argument version (now supported in CPython) -print(binascii.hexlify(b"123", ":")) +print(hexlify(b"123", ":")) # zero length buffer -print(binascii.hexlify(b"", b":")) +print(hexlify(b"", b":")) diff --git a/tests/extmod/binascii_unhexlify.py b/tests/extmod/binascii_unhexlify.py index bb663bc5b0c0d..ec31fb2325aa3 100644 --- a/tests/extmod/binascii_unhexlify.py +++ b/tests/extmod/binascii_unhexlify.py @@ -1,5 +1,5 @@ try: - import binascii + from binascii import unhexlify except ImportError: print("SKIP") raise SystemExit @@ -10,14 +10,14 @@ b"7f80ff", b"313233344142434461626364", ): - print(binascii.unhexlify(x)) + print(unhexlify(x)) try: - a = binascii.unhexlify(b"0") # odd buffer length + a = unhexlify(b"0") # odd buffer length except ValueError: print("ValueError") try: - a = binascii.unhexlify(b"gg") # digit not hex + a = unhexlify(b"gg") # digit not hex except ValueError: print("ValueError") From a081b2e151565550508ac782ef15db8e9d2a7eff Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 24 Apr 2025 12:21:04 +1000 Subject: [PATCH 027/154] tests/extmod/vfs_lfs_ilistdir_del.py: Skip test if not enough memory. Signed-off-by: Damien George --- tests/extmod/vfs_lfs_ilistdir_del.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/extmod/vfs_lfs_ilistdir_del.py b/tests/extmod/vfs_lfs_ilistdir_del.py index 7b59bc412d983..f463b84b22492 100644 --- a/tests/extmod/vfs_lfs_ilistdir_del.py +++ b/tests/extmod/vfs_lfs_ilistdir_del.py @@ -71,5 +71,10 @@ def test(bdev, vfs_class): fs.open("/test", "w").close() -bdev = RAMBlockDevice(30) +try: + bdev = RAMBlockDevice(30) +except MemoryError: + print("SKIP") + raise SystemExit + test(bdev, vfs.VfsLfs2) From 898c04ae0e24ca523a630eecc9ac4b4787e55a7e Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 24 Apr 2025 12:21:27 +1000 Subject: [PATCH 028/154] tests/extmod/vfs_mountinfo.py: Don't import unused errno module. Signed-off-by: Damien George --- tests/extmod/vfs_mountinfo.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/extmod/vfs_mountinfo.py b/tests/extmod/vfs_mountinfo.py index f674e80763409..b31dc60ce76d7 100644 --- a/tests/extmod/vfs_mountinfo.py +++ b/tests/extmod/vfs_mountinfo.py @@ -5,7 +5,6 @@ except ImportError: print("SKIP") raise SystemExit -import errno class Filesystem: From ce7f65f96703bbbe6039ba29e079023a32dccef0 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 24 Apr 2025 05:07:28 +0200 Subject: [PATCH 029/154] tests/extmod/vfs_posix.py: Fix test on Android. This commit makes a slight change to the vfs_posix test suite to let it pass on Android. On Android, non-root processes can perform most filesystem operations only on a restricted set of directories. The vfs_posix test suite attempted to enumerate the filesystem root directory, and said directory happens to be restricted for non-root processes. This would raise an EACCES OSError and terminate the test with a unexpected failure. To fix this, rather than enumerating the filesystem root directory the enumeration target is the internal shared storage area root - which doesn't have enumeration restrictions for non-root processes. The path is hardcoded because it is guaranteed to be there on pretty much any recent-ish device for now (it stayed the same for more than a decade for compatibility reasons). The proper way would be to query the storage subsystem via a JNI round-trip call, but this introduces too much complexity for something that is unlikely to break going forward. Signed-off-by: Alessandro Gatti --- tests/extmod/vfs_posix.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/extmod/vfs_posix.py b/tests/extmod/vfs_posix.py index d060c0b9c84f3..b3ca2753ba9cf 100644 --- a/tests/extmod/vfs_posix.py +++ b/tests/extmod/vfs_posix.py @@ -29,7 +29,21 @@ print(type(os.stat("/"))) # listdir and ilistdir -print(type(os.listdir("/"))) +target = "/" +try: + import platform + + # On Android non-root users are permitted full filesystem access only to + # selected directories. To let this test pass on bionic, the internal + # user-accessible storage area root is enumerated instead of the + # filesystem root. "/storage/emulated/0" should be there on pretty much + # any recent-ish device; querying the proper location requires a JNI + # round-trip, not really worth it. + if platform.platform().startswith("Android-"): + target = "/storage/emulated/0" +except ImportError: + pass +print(type(os.listdir(target))) # mkdir os.mkdir(temp_dir) From 584fa8800b833b53c6d6f9eb7572774a67304d2f Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 24 Apr 2025 17:52:43 +1000 Subject: [PATCH 030/154] esp32/tools: Update metrics_esp32 script for ESP-IDF >=v5.4.x. The output of 'idf.py size' has changed, plus some other cleanups around build dir name, etc. Can now run on v5.2.2 and v5.4.1, probably other versions. Signed-off-by: Angus Gratton --- ports/esp32/tools/metrics_esp32.py | 46 +++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/ports/esp32/tools/metrics_esp32.py b/ports/esp32/tools/metrics_esp32.py index 66a6a588ba212..5e65a78c97a27 100755 --- a/ports/esp32/tools/metrics_esp32.py +++ b/ports/esp32/tools/metrics_esp32.py @@ -32,6 +32,7 @@ # column of the table is really D/IRAM. import os import re +import shutil import sys import subprocess from dataclasses import dataclass @@ -47,6 +48,13 @@ ) +def rmtree(path): + try: + shutil.rmtree(path) + except FileNotFoundError: + pass + + @dataclass class BuildSizes: idf_ver: str @@ -99,7 +107,7 @@ def __lt__(self, other): def build_dir(self): if self.variant: - return f"build-{self.board}_{self.variant}" + return f"build-{self.board}-{self.variant}" else: return f"build-{self.board}" @@ -124,12 +132,23 @@ def run_make(self, target): def make_size(self): try: size_out = self.run_make("size") - # "Used static DRAM:" or "Used stat D/IRAM:" - RE_DRAM = r"Used stat(?:ic)? D.*: *(\d+) bytes" - RE_IRAM = r"Used static IRAM: *(\d+) bytes" + try: + # pre IDF v5.4 size output + # "Used static DRAM:" or "Used stat D/IRAM:" + RE_DRAM = r"Used stat(?:ic)? D.*: *(\d+) bytes" + RE_IRAM = r"Used static IRAM: *(\d+) bytes" + self.dram_size = re.search(RE_DRAM, size_out).group(1) + self.iram_size = re.search(RE_IRAM, size_out).group(1) + except AttributeError: + # IDF v5.4 size output is much nicer formatted + # Note the pipes in these expressions are not the ASCII/RE | + RE_DRAM = r"│ *DI?RAM *│ *(\d+)" + RE_IRAM = r"│ *IRAM *│ *(\d+)" + self.dram_size = re.search(RE_DRAM, size_out).group(1) + self.iram_size = re.search(RE_IRAM, size_out).group(1) + + # This line is the same on before/after versions RE_BIN = r"Total image size: *(\d+) bytes" - self.dram_size = re.search(RE_DRAM, size_out).group(1) - self.iram_size = re.search(RE_IRAM, size_out).group(1) self.bin_size = re.search(RE_BIN, size_out).group(1) except subprocess.CalledProcessError: self.bin_size = "build failed" @@ -139,13 +158,26 @@ def main(do_clean): if "IDF_PATH" not in os.environ: raise RuntimeError("IDF_PATH must be set") + if not os.path.exists("Makefile"): + raise RuntimeError( + "This script must be run from the ports/esp32 directory, i.e. as ./tools/metrics_esp32.py" + ) + + if "IDF_PYTHON_ENV_PATH" in os.environ: + raise RuntimeError( + "Run this script without any existing ESP-IDF environment active/exported." + ) + sizes = [] for idf_ver in IDF_VERS: switch_ver(idf_ver) + rmtree("managed_components") for board, variant in BUILDS: print(f"Building '{board}'/'{variant}'...", file=sys.stderr) result = BuildSizes(idf_ver, board, variant) - result.run_make("clean") + # Rather than running the 'clean' target, delete the build directory to avoid + # environment version mismatches, etc. + rmtree(result.build_dir()) result.make_size() result.print_summary() sizes.append(result) From dcca3ff602c821e5b252cbe44e669806b0a6f416 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 17 Apr 2025 10:20:33 +1000 Subject: [PATCH 031/154] tools/mpremote: Use zlib.compressobj instead of zlib.compress. Because the `wbits` parameter was only added to `zlib.compress` in CPython 3.11. Using `zlib.compressobj` makes the code compatible with much older CPython versions. Fixes issue #17140. Signed-off-by: Damien George --- tools/mpremote/mpremote/commands.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/mpremote/mpremote/commands.py b/tools/mpremote/mpremote/commands.py index 690b2ea723ea4..1e13b33afe7de 100644 --- a/tools/mpremote/mpremote/commands.py +++ b/tools/mpremote/mpremote/commands.py @@ -649,7 +649,9 @@ def _do_romfs_deploy(state, args): romfs_chunk += bytes(chunk_size - len(romfs_chunk)) if has_deflate_io: # Needs: binascii.a2b_base64, io.BytesIO, deflate.DeflateIO. - romfs_chunk_compressed = zlib.compress(romfs_chunk, wbits=-9) + compressor = zlib.compressobj(wbits=-9) + romfs_chunk_compressed = compressor.compress(romfs_chunk) + romfs_chunk_compressed += compressor.flush() buf = binascii.b2a_base64(romfs_chunk_compressed).strip() transport.exec(f"buf=DeflateIO(BytesIO(a2b_base64({buf})),RAW,9).read()") elif has_a2b_base64: From 076e07197e35cdc0e23bd6f45fd21a36e2162e88 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 27 Mar 2025 11:44:09 +1100 Subject: [PATCH 032/154] lib/lwip: Update lwIP to STABLE-2_2_1_RELEASE. This updates lwIP from STABLE-2_2_0_RELEASE, which was released in September 2023. The latest STABLE-2_2_1_RELEASE was released in February 2025. Signed-off-by: Damien George --- lib/lwip | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/lwip b/lib/lwip index 0a0452b2c39bd..77dcd25a72509 160000 --- a/lib/lwip +++ b/lib/lwip @@ -1 +1 @@ -Subproject commit 0a0452b2c39bdd91e252aef045c115f88f6ca773 +Subproject commit 77dcd25a72509eb83f72b033d219b1d40cd8eb95 From 02eea0da2448f2be9bcc04f340a6a0a1fae18f7b Mon Sep 17 00:00:00 2001 From: stijn Date: Tue, 22 Apr 2025 14:53:07 +0200 Subject: [PATCH 033/154] py: Make struct-initializing macros compatible with C++. This requires explicitly naming and initializing all members so add that where needed and possible. For MP_DEFINE_NLR_JUMP_CALLBACK_FUNCTION_1 this would require initializing the .callback member, but that's a bit of a waste since the macro is always followed by a call to nlr_push_jump_callback() to initialize exactly that member, so rewrite the macro without initializers. Signed-off-by: stijn --- py/obj.h | 14 +++++++------- py/objtuple.h | 2 +- py/runtime.h | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/py/obj.h b/py/obj.h index 93d1e0ee34349..07362522474d6 100644 --- a/py/obj.h +++ b/py/obj.h @@ -371,25 +371,25 @@ typedef struct _mp_rom_obj_t { mp_const_obj_t o; } mp_rom_obj_t; #define MP_DEFINE_CONST_FUN_OBJ_0(obj_name, fun_name) \ const mp_obj_fun_builtin_fixed_t obj_name = \ - {{&mp_type_fun_builtin_0}, .fun._0 = fun_name} + {.base = {.type = &mp_type_fun_builtin_0}, .fun = {._0 = fun_name}} #define MP_DEFINE_CONST_FUN_OBJ_1(obj_name, fun_name) \ const mp_obj_fun_builtin_fixed_t obj_name = \ - {{&mp_type_fun_builtin_1}, .fun._1 = fun_name} + {.base = {.type = &mp_type_fun_builtin_1}, .fun = {._1 = fun_name}} #define MP_DEFINE_CONST_FUN_OBJ_2(obj_name, fun_name) \ const mp_obj_fun_builtin_fixed_t obj_name = \ - {{&mp_type_fun_builtin_2}, .fun._2 = fun_name} + {.base = {.type = &mp_type_fun_builtin_2}, .fun = {._2 = fun_name}} #define MP_DEFINE_CONST_FUN_OBJ_3(obj_name, fun_name) \ const mp_obj_fun_builtin_fixed_t obj_name = \ - {{&mp_type_fun_builtin_3}, .fun._3 = fun_name} + {.base = {.type = &mp_type_fun_builtin_3}, .fun = {._3 = fun_name}} #define MP_DEFINE_CONST_FUN_OBJ_VAR(obj_name, n_args_min, fun_name) \ const mp_obj_fun_builtin_var_t obj_name = \ - {{&mp_type_fun_builtin_var}, MP_OBJ_FUN_MAKE_SIG(n_args_min, MP_OBJ_FUN_ARGS_MAX, false), .fun.var = fun_name} + {.base = {.type = &mp_type_fun_builtin_var}, .sig = MP_OBJ_FUN_MAKE_SIG(n_args_min, MP_OBJ_FUN_ARGS_MAX, false), .fun = {.var = fun_name}} #define MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(obj_name, n_args_min, n_args_max, fun_name) \ const mp_obj_fun_builtin_var_t obj_name = \ - {{&mp_type_fun_builtin_var}, MP_OBJ_FUN_MAKE_SIG(n_args_min, n_args_max, false), .fun.var = fun_name} + {.base = {.type = &mp_type_fun_builtin_var}, .sig = MP_OBJ_FUN_MAKE_SIG(n_args_min, n_args_max, false), .fun = {.var = fun_name}} #define MP_DEFINE_CONST_FUN_OBJ_KW(obj_name, n_args_min, fun_name) \ const mp_obj_fun_builtin_var_t obj_name = \ - {{&mp_type_fun_builtin_var}, MP_OBJ_FUN_MAKE_SIG(n_args_min, MP_OBJ_FUN_ARGS_MAX, true), .fun.kw = fun_name} + {.base = {.type = &mp_type_fun_builtin_var}, .sig = MP_OBJ_FUN_MAKE_SIG(n_args_min, MP_OBJ_FUN_ARGS_MAX, true), .fun = {.kw = fun_name}} // These macros are used to define constant map/dict objects // You can put "static" in front of the definition to make it local diff --git a/py/objtuple.h b/py/objtuple.h index 034814b82662d..3c82a9edcf345 100644 --- a/py/objtuple.h +++ b/py/objtuple.h @@ -50,7 +50,7 @@ extern const mp_obj_type_t mp_type_attrtuple; #define MP_DEFINE_ATTRTUPLE(tuple_obj_name, fields, nitems, ...) \ const mp_rom_obj_tuple_t tuple_obj_name = { \ - .base = {&mp_type_attrtuple}, \ + .base = {.type = &mp_type_attrtuple}, \ .len = nitems, \ .items = { __VA_ARGS__, MP_ROM_PTR((void *)fields) } \ } diff --git a/py/runtime.h b/py/runtime.h index e8e5a758f8beb..77cdd0e203da9 100644 --- a/py/runtime.h +++ b/py/runtime.h @@ -30,12 +30,12 @@ #include "py/pystack.h" #include "py/cstack.h" -// For use with mp_call_function_1_from_nlr_jump_callback. +// Initialize an nlr_jump_callback_node_call_function_1_t struct for use with +// nlr_push_jump_callback(&ctx.callback, mp_call_function_1_from_nlr_jump_callback); #define MP_DEFINE_NLR_JUMP_CALLBACK_FUNCTION_1(ctx, f, a) \ - nlr_jump_callback_node_call_function_1_t ctx = { \ - .func = (void (*)(void *))(f), \ - .arg = (a), \ - } + nlr_jump_callback_node_call_function_1_t ctx; \ + ctx.func = (void (*)(void *))(f); \ + ctx.arg = (a) typedef enum { MP_VM_RETURN_NORMAL, From 9a377801dc5a99825d6d613f6affcafe059306c0 Mon Sep 17 00:00:00 2001 From: stijn Date: Tue, 22 Apr 2025 13:31:45 +0200 Subject: [PATCH 034/154] unix/coveragecpp: Verify struct-initializing macros' C++-compatibility. Add code using all relevant macros to make sure they initialize structs correctly. Signed-off-by: stijn --- ports/unix/coveragecpp.cpp | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/ports/unix/coveragecpp.cpp b/ports/unix/coveragecpp.cpp index 23c3955ae9d0b..8ba308f6468f3 100644 --- a/ports/unix/coveragecpp.cpp +++ b/ports/unix/coveragecpp.cpp @@ -17,10 +17,61 @@ extern "C" { #include } +// Invoke all (except one, see below) public API macros which initialize structs to make sure +// they are C++-compatible, meaning they explicitly initialize all struct members. +mp_obj_t f0(); +MP_DEFINE_CONST_FUN_OBJ_0(f0_obj, f0); +mp_obj_t f1(mp_obj_t); +MP_DEFINE_CONST_FUN_OBJ_1(f1_obj, f1); +mp_obj_t f2(mp_obj_t, mp_obj_t); +MP_DEFINE_CONST_FUN_OBJ_2(f2_obj, f2); +mp_obj_t f3(mp_obj_t, mp_obj_t, mp_obj_t); +MP_DEFINE_CONST_FUN_OBJ_3(f3_obj, f3); +mp_obj_t fvar(size_t, const mp_obj_t *); +MP_DEFINE_CONST_FUN_OBJ_VAR(fvar_obj, 1, fvar); +mp_obj_t fvarbetween(size_t, const mp_obj_t *); +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fvarbetween_obj, 1, 2, fvarbetween); +mp_obj_t fkw(size_t, const mp_obj_t *, mp_map_t *); +MP_DEFINE_CONST_FUN_OBJ_KW(fkw_obj, 1, fkw); + +static const mp_rom_map_elem_t table[] = { + { MP_ROM_QSTR(MP_QSTR_f0), MP_ROM_PTR(&f0_obj) }, +}; +MP_DEFINE_CONST_MAP(map, table); +MP_DEFINE_CONST_DICT(dict, table); + +static const qstr attrtuple_fields[] = { + MP_QSTR_f0, +}; +MP_DEFINE_ATTRTUPLE(attrtuple, attrtuple_fields, 1, MP_ROM_PTR(&f0_obj)); + +void nlr_cb(void *); +void nlr_cb(void *){ +} + +// The MP_DEFINE_CONST_OBJ_TYPE macro is not C++-compatible because each of the +// MP_DEFINE_CONST_OBJ_TYPE_NARGS_X macros only initializes some of _mp_obj_type_t's +// .slot_index_xxx members but that cannot be fixed to be done in a deterministic way. + + #if defined(MICROPY_UNIX_COVERAGE) // Just to test building of C++ code. static mp_obj_t extra_cpp_coverage_impl() { + MP_DEFINE_NLR_JUMP_CALLBACK_FUNCTION_1(ctx, nlr_cb, (void *) nlr_cb); + + // To avoid 'error: unused variable [-Werror,-Wunused-const-variable]'. + (void) ctx; + (void) f0_obj; + (void) f1_obj; + (void) f2_obj; + (void) f3_obj; + (void) fvar_obj; + (void) fvarbetween_obj; + (void) fkw_obj; + (void) map; + (void) dict; + (void) attrtuple; return mp_const_none; } From cee0419021396a87bd38bb3231b06311f604fad4 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Tue, 22 Apr 2025 09:08:14 -0400 Subject: [PATCH 035/154] tools/mpremote/tests: Add tests for errno behavior. Signed-off-by: Anson Mansfield --- tools/mpremote/tests/test_errno.sh | 38 ++++++++++++++++++++++++++ tools/mpremote/tests/test_errno.sh.exp | 25 +++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100755 tools/mpremote/tests/test_errno.sh create mode 100644 tools/mpremote/tests/test_errno.sh.exp diff --git a/tools/mpremote/tests/test_errno.sh b/tools/mpremote/tests/test_errno.sh new file mode 100755 index 0000000000000..0899706552d14 --- /dev/null +++ b/tools/mpremote/tests/test_errno.sh @@ -0,0 +1,38 @@ +#!/bin/bash +set -e + +# Special ErrorFS so the test can induce arbitrary filesystem errors. +cat << EOF > "${TMP}/fs.py" +import os, vfs, errno + +class ErrorFS: + def mount(self, *a, **k): + pass + def umount(self, *a, **k): + pass + def chdir(self, *a, **k): + pass + def open(self, *a, **k): + raise self.error + +fs = ErrorFS() +vfs.mount(fs, '/fs') +os.chdir('/fs') +EOF + +$MPREMOTE run "${TMP}/fs.py" + +echo ----- +$MPREMOTE resume exec "fs.error = Exception()" +( + $MPREMOTE resume cat :Exception.py || echo "expect error" +) 2> >(head -n1 >&2) # discard traceback specifics but keep main error message + +for errno in ENOENT EISDIR EEXIST ENODEV EINVAL EPERM EOPNOTSUPP ; do +echo ----- +$MPREMOTE resume exec "fs.error = OSError(errno.$errno, '')" +$MPREMOTE resume cat :$errno.py || echo "expect error" +done + +echo ----- +$MPREMOTE resume exec "vfs.umount('/fs')" diff --git a/tools/mpremote/tests/test_errno.sh.exp b/tools/mpremote/tests/test_errno.sh.exp new file mode 100644 index 0000000000000..deda52f5fb0e8 --- /dev/null +++ b/tools/mpremote/tests/test_errno.sh.exp @@ -0,0 +1,25 @@ +----- +mpremote: Error with transport: +expect error +----- +mpremote: cat: ENOENT.py: No such file or directory. +expect error +----- +mpremote: cat: EISDIR.py: Is a directory. +expect error +----- +mpremote: cat: EEXIST.py: File exists. +expect error +----- +mpremote: cat: ENODEV.py: No such device. +expect error +----- +mpremote: cat: EINVAL.py: Invalid argument. +expect error +----- +mpremote: cat: EPERM.py: Operation not permitted. +expect error +----- +mpremote: cat: EOPNOTSUPP.py: Operation not supported. +expect error +----- From 805fe083a3dea76b0acd36ecc436c2b76b808059 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Mon, 7 Apr 2025 10:08:36 -0400 Subject: [PATCH 036/154] tools/mpremote: Refactor error handling to apply generally to any errno. This rewrites the code that previously manually emitted and caught various OSError subclasses with equivalent code that uses the errno name dictionary to do this generically, and updates the exception handler in do_filesystem to catch them in a similarly-generic fashion using os.strerror to retrieve an appropriate error message text equivalent to the current messages. Note that in the CPython environments where mpremote runs, the call to the OSError constructor already returns an instance of the corresponding mapped exception subtype. Signed-off-by: Anson Mansfield --- tools/mpremote/mpremote/commands.py | 8 ++------ tools/mpremote/mpremote/transport.py | 16 ++++------------ 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/tools/mpremote/mpremote/commands.py b/tools/mpremote/mpremote/commands.py index 1e13b33afe7de..b1e5d3c7ed664 100644 --- a/tools/mpremote/mpremote/commands.py +++ b/tools/mpremote/mpremote/commands.py @@ -393,12 +393,8 @@ def do_filesystem(state, args): ) else: do_filesystem_cp(state, path, cp_dest, len(paths) > 1, not args.force) - except FileNotFoundError as er: - raise CommandError("{}: {}: No such file or directory.".format(command, er.args[0])) - except IsADirectoryError as er: - raise CommandError("{}: {}: Is a directory.".format(command, er.args[0])) - except FileExistsError as er: - raise CommandError("{}: {}: File exists.".format(command, er.args[0])) + except OSError as er: + raise CommandError("{}: {}: {}.".format(command, er.strerror, os.strerror(er.errno))) except TransportError as er: raise CommandError("Error with transport:\n{}".format(er.args[0])) diff --git a/tools/mpremote/mpremote/transport.py b/tools/mpremote/mpremote/transport.py index 8d30c7f517ff9..df8ef209aecf6 100644 --- a/tools/mpremote/mpremote/transport.py +++ b/tools/mpremote/mpremote/transport.py @@ -55,18 +55,10 @@ def __init__(self, status_code, error_output): # Takes a Transport error (containing the text of an OSError traceback) and # raises it as the corresponding OSError-derived exception. def _convert_filesystem_error(e, info): - if "OSError" in e.error_output and "ENOENT" in e.error_output: - return FileNotFoundError(info) - if "OSError" in e.error_output and "EISDIR" in e.error_output: - return IsADirectoryError(info) - if "OSError" in e.error_output and "EEXIST" in e.error_output: - return FileExistsError(info) - if "OSError" in e.error_output and "ENODEV" in e.error_output: - return FileNotFoundError(info) - if "OSError" in e.error_output and "EINVAL" in e.error_output: - return OSError(errno.EINVAL, info) - if "OSError" in e.error_output and "EPERM" in e.error_output: - return OSError(errno.EPERM, info) + if "OSError" in e.error_output: + for code, estr in errno.errorcode.items(): + if estr in e.error_output: + return OSError(code, info) return e From dc46cf15c17ab5bd8371c00e11ee9743229b7868 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Mon, 7 Apr 2025 13:52:57 -0400 Subject: [PATCH 037/154] tools/mpremote: Fix possibly-missing EOPNOTSUPP errno name. Signed-off-by: Anson Mansfield --- tools/mpremote/mpremote/transport.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/mpremote/mpremote/transport.py b/tools/mpremote/mpremote/transport.py index df8ef209aecf6..1b70f9b2edc40 100644 --- a/tools/mpremote/mpremote/transport.py +++ b/tools/mpremote/mpremote/transport.py @@ -56,7 +56,10 @@ def __init__(self, status_code, error_output): # raises it as the corresponding OSError-derived exception. def _convert_filesystem_error(e, info): if "OSError" in e.error_output: - for code, estr in errno.errorcode.items(): + for code, estr in [ + *errno.errorcode.items(), + (errno.EOPNOTSUPP, "EOPNOTSUPP"), + ]: if estr in e.error_output: return OSError(code, info) return e From 37fe3f66c3eaac084d9da5b92fabdf84977142f9 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 17 Apr 2025 17:14:16 +0200 Subject: [PATCH 038/154] tools/mpremote/tests: Add test for rm -r on /remote vfs. Signed-off-by: Jos Verlinde --- tools/mpremote/tests/test_filesystem.sh | 5 ++++- tools/mpremote/tests/test_filesystem.sh.exp | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/mpremote/tests/test_filesystem.sh b/tools/mpremote/tests/test_filesystem.sh index a20d77dfea399..a29015e987268 100755 --- a/tools/mpremote/tests/test_filesystem.sh +++ b/tools/mpremote/tests/test_filesystem.sh @@ -233,4 +233,7 @@ $MPREMOTE resume exec "import os;os.chdir('/')" $MPREMOTE resume rm -r -v :/ramdisk $MPREMOTE resume ls :/ramdisk -echo ----- \ No newline at end of file +echo ----- +# try to delete existing folder in mounted filesystem +$MPREMOTE mount "${TMP}" + rm -rv :package || echo "expect error" +echo ----- diff --git a/tools/mpremote/tests/test_filesystem.sh.exp b/tools/mpremote/tests/test_filesystem.sh.exp index 16d98c4ded8ec..3d9d0fe9ae8fa 100644 --- a/tools/mpremote/tests/test_filesystem.sh.exp +++ b/tools/mpremote/tests/test_filesystem.sh.exp @@ -267,3 +267,8 @@ removed directory: '/ramdisk/package' skipped: '/ramdisk' (vfs mountpoint) ls :/ramdisk ----- +Local directory ${TMP} is mounted at /remote +rm :package +mpremote: rm -r not permitted on /remote directory +expect error +----- From 6406afb1f3ad8216ef205098880b2e602104af5f Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 17 Apr 2025 16:52:23 +0200 Subject: [PATCH 039/154] tools/mpremote: Prevent deletion of /remote files via rm -r. Removes the risk of inadvertently deleting files on the host by preventing the deletion of files via `rm -r` on the `/remote` vfs mount point. Fixes issue #17147. Signed-off-by: Jos Verlinde --- tools/mpremote/mpremote/commands.py | 11 +++++++++++ tools/mpremote/mpremote/transport_serial.py | 16 +++++++++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/tools/mpremote/mpremote/commands.py b/tools/mpremote/mpremote/commands.py index b1e5d3c7ed664..452384728ab71 100644 --- a/tools/mpremote/mpremote/commands.py +++ b/tools/mpremote/mpremote/commands.py @@ -303,6 +303,17 @@ def _mkdir(a, *b): def do_filesystem_recursive_rm(state, path, args): if state.transport.fs_isdir(path): + if state.transport.mounted: + r_cwd = state.transport.eval("os.getcwd()") + abs_path = os.path.normpath( + os.path.join(r_cwd, path) if not os.path.isabs(path) else path + ) + if isinstance(state.transport, SerialTransport) and abs_path.startswith( + f'{SerialTransport.fs_hook_mount}/' + ): + raise CommandError( + f"rm -r not permitted on {SerialTransport.fs_hook_mount} directory" + ) for entry in state.transport.fs_listdir(path): do_filesystem_recursive_rm(state, _remote_path_join(path, entry.name), args) if path: diff --git a/tools/mpremote/mpremote/transport_serial.py b/tools/mpremote/mpremote/transport_serial.py index 6aed0bb496b93..53fc48553b167 100644 --- a/tools/mpremote/mpremote/transport_serial.py +++ b/tools/mpremote/mpremote/transport_serial.py @@ -42,6 +42,8 @@ class SerialTransport(Transport): + fs_hook_mount = "/remote" # MUST match the mount point in fs_hook_code + def __init__(self, device, baudrate=115200, wait=0, exclusive=True, timeout=None): self.in_raw_repl = False self.use_raw_paste = True @@ -375,7 +377,11 @@ def write_ctrl_d(self, out_callback): self.serial = self.serial.orig_serial # Provide a message about the remount. - out_callback(bytes(f"\r\nRemount local directory {self.cmd.root} at /remote\r\n", "utf8")) + out_callback( + bytes( + f"\r\nRemount local directory {self.cmd.root} at {self.fs_hook_mount}\r\n", "utf8" + ) + ) # Enter raw REPL and re-mount the remote filesystem. self.serial.write(b"\x01") @@ -392,7 +398,7 @@ def write_ctrl_d(self, out_callback): def umount_local(self): if self.mounted: - self.exec('os.umount("/remote")') + self.exec(f'os.umount("{self.fs_hook_mount}")') self.mounted = False self.serial = self.serial.orig_serial @@ -413,7 +419,7 @@ def umount_local(self): "CMD_RMDIR": 13, } -fs_hook_code = """\ +fs_hook_code = f"""\ import os, io, struct, micropython SEEK_SET = 0 @@ -746,8 +752,8 @@ def open(self, path, mode): def __mount(): - os.mount(RemoteFS(RemoteCommand()), '/remote') - os.chdir('/remote') + os.mount(RemoteFS(RemoteCommand()), '{SerialTransport.fs_hook_mount}') + os.chdir('{SerialTransport.fs_hook_mount}') """ # Apply basic compression on hook code. From cd71db0172c9f0905fd3d3438b1f2cf70da11dfa Mon Sep 17 00:00:00 2001 From: Detlev Zundel Date: Thu, 6 Feb 2025 16:44:39 +0100 Subject: [PATCH 040/154] zephyr: Fix prj.conf for v4.1-rc1. The (deprecated) kconfig option NET_SOCKETS_POSIX_NAMES was removed in commit abad505bdeed6102061767f45acd63323973f564 so remove it from our configuration. As the option has been deprecated longer, this also works for v3.7 and v4.0 the other still supported versions. Signed-off-by: Detlev Zundel --- ports/zephyr/prj.conf | 1 - 1 file changed, 1 deletion(-) diff --git a/ports/zephyr/prj.conf b/ports/zephyr/prj.conf index ae37c3ff07614..0325cddd206c4 100644 --- a/ports/zephyr/prj.conf +++ b/ports/zephyr/prj.conf @@ -29,7 +29,6 @@ CONFIG_NET_IPV6=y CONFIG_NET_UDP=y CONFIG_NET_TCP=y CONFIG_NET_SOCKETS=y -CONFIG_NET_SOCKETS_POSIX_NAMES=n CONFIG_TEST_RANDOM_GENERATOR=y CONFIG_NET_CONFIG_SETTINGS=y From cd3eaad05cf94cd86e9762c854c68a4d4b5c2fab Mon Sep 17 00:00:00 2001 From: Detlev Zundel Date: Thu, 6 Feb 2025 16:45:25 +0100 Subject: [PATCH 041/154] zephyr: Fix call to thread_analyzer_print for v4.0. Commit 1b6e0f64796dfd6f86a8679ea6d24e1fca1e63a8 for Zephyr v4.0.0 changed the function "thread_analyzer_print" to require a cpu argument and allow thread analysis on each cpu separately. The argument is ignored when THREAD_ANALYZER_AUTO_SEPARATE_CORES=n which is the default on single core machines. Promote this change to the MicroPython zephyr module. Signed-off-by: Detlev Zundel Signed-off-by: Maureen Helm --- docs/library/zephyr.rst | 8 ++++++-- ports/zephyr/modzephyr.c | 10 ++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/library/zephyr.rst b/docs/library/zephyr.rst index 10676d9085289..1a106d50ea717 100644 --- a/docs/library/zephyr.rst +++ b/docs/library/zephyr.rst @@ -22,9 +22,10 @@ Functions Returns the thread id of the current thread, which is used to reference the thread. -.. function:: thread_analyze() +.. function:: thread_analyze(cpu) - Runs the Zephyr debug thread analyzer on the current thread and prints stack size statistics in the format: + Runs the Zephyr debug thread analyzer on the current thread on the given cpu + and prints stack size statistics in the format: "``thread_name``-20s: STACK: unused ``available_stack_space`` usage ``stack_space_used`` / ``stack_size`` (``percent_stack_space_used`` %); CPU: ``cpu_utilization`` %" @@ -35,6 +36,9 @@ Functions For more information, see documentation for Zephyr `thread analyzer `_. + Note that the ``cpu`` argument is only used in Zephyr v4.0.0 and + newer and ignored otherwise. + .. function:: shell_exec(cmd_in) Executes the given command on an UART backend. This function can only be accessed if ``CONFIG_SHELL_BACKEND_SERIAL`` diff --git a/ports/zephyr/modzephyr.c b/ports/zephyr/modzephyr.c index c059c7e395796..08fdf5c5aa44d 100644 --- a/ports/zephyr/modzephyr.c +++ b/ports/zephyr/modzephyr.c @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -48,11 +49,16 @@ static mp_obj_t mod_current_tid(void) { static MP_DEFINE_CONST_FUN_OBJ_0(mod_current_tid_obj, mod_current_tid); #ifdef CONFIG_THREAD_ANALYZER -static mp_obj_t mod_thread_analyze(void) { +static mp_obj_t mod_thread_analyze(mp_obj_t cpu_in) { + #if KERNEL_VERSION_NUMBER >= ZEPHYR_VERSION(4, 0, 0) + unsigned int cpu = mp_obj_get_int(cpu_in); + thread_analyzer_print(cpu); + #else thread_analyzer_print(); + #endif return mp_const_none; } -static MP_DEFINE_CONST_FUN_OBJ_0(mod_thread_analyze_obj, mod_thread_analyze); +static MP_DEFINE_CONST_FUN_OBJ_1(mod_thread_analyze_obj, mod_thread_analyze); #endif #ifdef CONFIG_SHELL_BACKEND_SERIAL From b83606fe337614543f18f0ddf4a3250974b93d3c Mon Sep 17 00:00:00 2001 From: Detlev Zundel Date: Thu, 6 Feb 2025 16:40:14 +0100 Subject: [PATCH 042/154] zephyr: Remove reference to CONFIG_MMC_VOLUME_NAME for v4.0. Commit 07a8e3253a2d8a2076c9c83c4ed4158fa3fbb2a2 removes CONFIG_MMC_VOLUME_NAME from the Kconfig space. Instead we need to use the device tree to find the "disk-name" property of "zephyr,mmc-disk" devices. Signed-off-by: Detlev Zundel --- ports/zephyr/main.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/zephyr/main.c b/ports/zephyr/main.c index 9fae2b3a873e5..206b7f92d3917 100644 --- a/ports/zephyr/main.c +++ b/ports/zephyr/main.c @@ -30,6 +30,7 @@ #include #include +#include #ifdef CONFIG_NETWORKING #include #endif @@ -97,7 +98,11 @@ static void vfs_init(void) { int ret = 0; #ifdef CONFIG_DISK_DRIVER_SDMMC + #if KERNEL_VERSION_NUMBER >= ZEPHYR_VERSION(4, 0, 0) + mp_obj_t args[] = { mp_obj_new_str_from_cstr(DT_PROP(DT_INST(0, zephyr_sdmmc_disk), disk_name)) }; + #else mp_obj_t args[] = { mp_obj_new_str_from_cstr(CONFIG_SDMMC_VOLUME_NAME) }; + #endif bdev = MP_OBJ_TYPE_GET_SLOT(&zephyr_disk_access_type, make_new)(&zephyr_disk_access_type, ARRAY_SIZE(args), 0, args); mount_point_str = "/sd"; #elif defined(CONFIG_FLASH_MAP) && FIXED_PARTITION_EXISTS(storage_partition) From 3c8d1b13f5b7e60e491e3a19734c237e1485e413 Mon Sep 17 00:00:00 2001 From: Maureen Helm Date: Sun, 10 Nov 2024 08:24:02 -0600 Subject: [PATCH 043/154] zephyr: Upgrade to Zephyr v4.0.0. Updates the Zephyr port build instructions. The CI is updated to use Zephyr docker image 0.27.4, SDK 0.17.0 and the latest Zephyr release tag. Tested on max32690fthr and frdm_k64f. Signed-off-by: Maureen Helm Signed-off-by: Detlev Zundel --- docs/zephyr/tutorial/repl.rst | 2 +- ports/zephyr/README.md | 15 +++++++++------ tools/ci.sh | 6 +++--- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/docs/zephyr/tutorial/repl.rst b/docs/zephyr/tutorial/repl.rst index db7b7333d2416..199dda2b7aeee 100644 --- a/docs/zephyr/tutorial/repl.rst +++ b/docs/zephyr/tutorial/repl.rst @@ -31,7 +31,7 @@ With your serial program open (PuTTY, screen, picocom, etc) you may see a blank screen with a flashing cursor. Press Enter (or reset the board) and you should be presented with the following text:: - *** Booting Zephyr OS build v3.7.0 *** + *** Booting Zephyr OS build v4.0.0 *** MicroPython v1.24.0-preview.179.g5b85b24bd on 2024-08-05; zephyr-frdm_k64f with mk64f12 Type "help()" for more information. >>> diff --git a/ports/zephyr/README.md b/ports/zephyr/README.md index 4590eb7199c25..84adf96395246 100644 --- a/ports/zephyr/README.md +++ b/ports/zephyr/README.md @@ -4,10 +4,13 @@ MicroPython port to Zephyr RTOS This is a work-in-progress port of MicroPython to Zephyr RTOS (http://zephyrproject.org). -This port requires Zephyr version v3.7.0, and may also work on higher -versions. All boards supported -by Zephyr (with standard level of features support, like UART console) -should work with MicroPython (but not all were tested). +This port tries to support all Zephyr versions supported upstream, +i.e. currently v3.7 (LTS), v4.0 and the development branch. The CI is +setup to use the latest version, i.e. v4.0. + +All boards supported by Zephyr (with standard level of features +support, like UART console) should work with MicroPython (but not all +were tested). Features supported at this time: @@ -39,13 +42,13 @@ setup is correct. If you already have Zephyr installed but are having issues building the MicroPython port then try installing the correct version of Zephyr via: - $ west init zephyrproject -m https://github.com/zephyrproject-rtos/zephyr --mr v3.7.0 + $ west init zephyrproject -m https://github.com/zephyrproject-rtos/zephyr --mr v4.0.0 Alternatively, you don't have to redo the Zephyr installation to just switch from master to a tagged release, you can instead do: $ cd zephyrproject/zephyr - $ git checkout v3.7.0 + $ git checkout v4.0.0 $ west update With Zephyr installed you may then need to configure your environment, diff --git a/tools/ci.sh b/tools/ci.sh index cfc9754837f76..6f8d1cb80c4aa 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -818,9 +818,9 @@ function ci_windows_build { ######################################################################################## # ports/zephyr -ZEPHYR_DOCKER_VERSION=v0.26.13 -ZEPHYR_SDK_VERSION=0.16.8 -ZEPHYR_VERSION=v3.7.0 +ZEPHYR_DOCKER_VERSION=v0.27.4 +ZEPHYR_SDK_VERSION=0.17.0 +ZEPHYR_VERSION=v4.0.0 function ci_zephyr_setup { IMAGE=ghcr.io/zephyrproject-rtos/ci:${ZEPHYR_DOCKER_VERSION} From f4a7e713ea8b374de04c471d877e442bf6ff48e8 Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Sat, 19 Oct 2024 12:53:33 +0530 Subject: [PATCH 044/154] zephyr/machine_pwm: Implement PWM support. Implement PWM support using standard zephyr APIs, exposed as the standard MicroPython `machine.PWM` class. Signed-off-by: Ayush Singh --- ports/zephyr/README.md | 1 + ports/zephyr/machine_pwm.c | 205 ++++++++++++++++++++++++++++++++++++ ports/zephyr/mpconfigport.h | 2 + 3 files changed, 208 insertions(+) create mode 100644 ports/zephyr/machine_pwm.c diff --git a/ports/zephyr/README.md b/ports/zephyr/README.md index 84adf96395246..fc18d25c0aadb 100644 --- a/ports/zephyr/README.md +++ b/ports/zephyr/README.md @@ -19,6 +19,7 @@ Features supported at this time: * `machine.Pin` class for GPIO control, with IRQ support. * `machine.I2C` class for I2C control. * `machine.SPI` class for SPI control. +* `machine.PWM` class for PWM control * `socket` module for networking (IPv4/IPv6). * "Frozen modules" support to allow to bundle Python modules together with firmware. Including complete applications, including with diff --git a/ports/zephyr/machine_pwm.c b/ports/zephyr/machine_pwm.c new file mode 100644 index 0000000000000..5720dac9caf49 --- /dev/null +++ b/ports/zephyr/machine_pwm.c @@ -0,0 +1,205 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Ayush Singh + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "extmod/modmachine.h" +#include "py/runtime.h" +#include "stdint.h" +#include "zephyr/drivers/pwm.h" +#include "zephyr_device.h" + +#define VALUE_NOT_SET (-1) + +typedef struct _machine_pwm_obj_t { + mp_obj_base_t base; + const struct device *pwm; + uint32_t channel; + int32_t freq; + int32_t duty_u16; + int32_t duty_ns; + pwm_flags_t flags; +} machine_pwm_obj_t; + +static void configure_pwm(machine_pwm_obj_t *self) { + const uint32_t period = NSEC_PER_SEC / self->freq; + + if ((self->duty_u16 == VALUE_NOT_SET && self->duty_ns == VALUE_NOT_SET) || + self->freq == VALUE_NOT_SET) { + mp_raise_ValueError(MP_ERROR_TEXT("Frequency and duty values must be set")); + } + + if (self->duty_ns == VALUE_NOT_SET) { + self->duty_ns = (self->duty_u16 * period) / UINT16_MAX; + } + + if (self->duty_ns < 0) { + self->duty_ns = 0; + } else if (self->duty_ns > period) { + self->duty_ns = period; + } + + pwm_set(self->pwm, self->channel, period, self->duty_ns, self->flags); +} + +/******************************************************************************/ +// MicroPython bindings for PWM + +static void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, + mp_print_kind_t kind) { + machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "pwm, self->channel); + + if (self->duty_ns != VALUE_NOT_SET) { + mp_printf(print, " duty_ns=%d", self->duty_ns); + } else { + mp_printf(print, " duty_u16=%d", self->duty_u16); + } + + mp_printf(print, " freq=%d, flags=%u>", self->freq, self->flags); +} + +// This called from pwm.init() method +static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, size_t n_args, + const mp_obj_t *pos_args, + mp_map_t *kw_args) { + + enum { + ARG_freq, + ARG_duty_u16, + ARG_duty_ns, + ARG_invert, + }; + static const mp_arg_t allowed_args[] = { + {MP_QSTR_freq, MP_ARG_INT, {.u_int = VALUE_NOT_SET}}, + {MP_QSTR_duty_u16, MP_ARG_INT, {.u_int = VALUE_NOT_SET}}, + {MP_QSTR_duty_ns, MP_ARG_INT, {.u_int = VALUE_NOT_SET}}, + {MP_QSTR_invert, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1}}, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), + allowed_args, args); + + // Maybe change PWM timer + if (args[ARG_freq].u_int > 0) { + self->freq = args[ARG_freq].u_int; + } + + // Set duty_u16 cycle? + int32_t duty = args[ARG_duty_u16].u_int; + if (duty >= 0) { + self->duty_u16 = duty; + self->duty_ns = VALUE_NOT_SET; + } + // Set duty_ns value? + duty = args[ARG_duty_ns].u_int; + if (duty >= 0) { + self->duty_ns = duty; + self->duty_u16 = VALUE_NOT_SET; + } + + self->flags = 0; + if (args[ARG_invert].u_int >= 0) { + self->flags |= PWM_POLARITY_INVERTED; + } + + configure_pwm(self); +} + +// PWM(pin-tuple, freq, [args]) +static mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, + size_t n_args, size_t n_kw, + const mp_obj_t *args) { + mp_obj_t *items; + uint32_t wanted_chan; + const struct device *wanted_pwm; + + // Check number of arguments + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + + // Get referred Pin object(s) + if (!mp_obj_is_type(args[0], &mp_type_tuple)) { + mp_raise_ValueError( + MP_ERROR_TEXT("Pin id must be tuple of (\"pwm_x\", channel#)")); + } + + mp_obj_get_array_fixed_n(args[0], 2, &items); + wanted_pwm = zephyr_device_find(items[0]); + wanted_chan = mp_obj_get_int(items[1]); + + machine_pwm_obj_t *pwm = mp_obj_malloc(machine_pwm_obj_t, &machine_pwm_type); + pwm->pwm = wanted_pwm; + pwm->channel = wanted_chan; + pwm->duty_ns = VALUE_NOT_SET; + pwm->duty_u16 = VALUE_NOT_SET; + pwm->freq = VALUE_NOT_SET; + pwm->flags = 0; + + if (n_args > 1 || n_kw > 0) { + // pin mode given, so configure this GPIO + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + mp_machine_pwm_init_helper(pwm, n_args - 1, args + 1, &kw_args); + } + + return (mp_obj_t)pwm; +} + +// This called from pwm.deinit() method +static void mp_machine_pwm_deinit(machine_pwm_obj_t *self) { + self->duty_ns = 0; + self->duty_u16 = VALUE_NOT_SET; + configure_pwm(self); +} + +static mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self) { + return MP_OBJ_NEW_SMALL_INT(self->freq); +} + +static void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) { + self->freq = freq; + configure_pwm(self); +} + +static void mp_machine_pwm_duty_set_ns(machine_pwm_obj_t *self, + mp_int_t duty_ns) { + self->duty_ns = duty_ns; + self->duty_u16 = VALUE_NOT_SET; + configure_pwm(self); +} + +static mp_obj_t mp_machine_pwm_duty_get_ns(machine_pwm_obj_t *self) { + return MP_OBJ_NEW_SMALL_INT(self->duty_ns); +} + +static mp_obj_t mp_machine_pwm_duty_get_u16(machine_pwm_obj_t *self) { + return MP_OBJ_NEW_SMALL_INT(self->duty_u16); +} + +static void mp_machine_pwm_duty_set_u16(machine_pwm_obj_t *self, + mp_int_t duty_u16) { + self->duty_ns = VALUE_NOT_SET; + self->duty_u16 = duty_u16; + configure_pwm(self); +} diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index c4fa6c5b95439..e015776a4eb64 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -72,6 +72,8 @@ #define MICROPY_PY_MACHINE_WDT (1) #define MICROPY_PY_MACHINE_WDT_INCLUDEFILE "ports/zephyr/machine_wdt.c" #endif +#define MICROPY_PY_MACHINE_PWM (1) +#define MICROPY_PY_MACHINE_PWM_INCLUDEFILE "ports/zephyr/machine_pwm.c" #define MICROPY_PY_STRUCT (0) #ifdef CONFIG_NETWORKING // If we have networking, we likely want errno comfort From c9c39b88afe1d51b9bb4984cb19b787ac5834c52 Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Sat, 19 Oct 2024 13:13:39 +0530 Subject: [PATCH 045/154] docs/zephyr: Add quick reference for PWM support. Add docs for PWM support. Signed-off-by: Ayush Singh --- docs/zephyr/quickref.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/zephyr/quickref.rst b/docs/zephyr/quickref.rst index 63d4bced039fb..a7ae69860772d 100644 --- a/docs/zephyr/quickref.rst +++ b/docs/zephyr/quickref.rst @@ -56,6 +56,21 @@ Use the :ref:`machine.Pin ` class:: switch = Pin(("gpioc", 6), Pin.IN) # create input pin for a switch switch.irq(lambda t: print("SW2 changed")) # enable an interrupt when switch state is changed +PWM +--- + +Use the :ref:`machine.PWM ` class:: + + from machine import PWM + + pwm = PWM(("pwm0", 0), freq=3921568, duty_ns=200, invert=True) # create pwm on PWM0 + print(pwm) # print pwm + + print(pwm.duty_ns()) # print pwm duty cycle in nanoseconds + pwm.duty_ns(255) # set new pwm duty cycle in nanoseconds + + pwm.deinit() + Hardware I2C bus ---------------- From f9a755c91c8b002407ffb930d2bdaa7e85b2c350 Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Sat, 19 Oct 2024 12:27:14 +0530 Subject: [PATCH 046/154] zephyr/boards: Enable PWM on beagleconnect_freedom. Enable PWM config for bcf. Signed-off-by: Ayush Singh --- .../zephyr/boards/beagleconnect_freedom.conf | 1 + .../boards/beagleconnect_freedom.overlay | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 ports/zephyr/boards/beagleconnect_freedom.overlay diff --git a/ports/zephyr/boards/beagleconnect_freedom.conf b/ports/zephyr/boards/beagleconnect_freedom.conf index 14ce9c526e059..1e3f6037bd8da 100644 --- a/ports/zephyr/boards/beagleconnect_freedom.conf +++ b/ports/zephyr/boards/beagleconnect_freedom.conf @@ -1,4 +1,5 @@ # Hardware features +CONFIG_PWM=y CONFIG_I2C=y CONFIG_SPI=y diff --git a/ports/zephyr/boards/beagleconnect_freedom.overlay b/ports/zephyr/boards/beagleconnect_freedom.overlay new file mode 100644 index 0000000000000..7dd4469c99247 --- /dev/null +++ b/ports/zephyr/boards/beagleconnect_freedom.overlay @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 Ayush Singh + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&pinctrl { + /* MB1 PWM */ + pwm0_default: pwm0_default { + pinmux = <17 IOC_PORT_MCU_PORT_EVENT1>; + bias-disable; + drive-strength = <2>; + }; + + /* MB2 PWM */ + pwm1_default: pwm1_default { + pinmux = <19 IOC_PORT_MCU_PORT_EVENT3>; + bias-disable; + drive-strength = <2>; + }; +}; + +&gpt0 { + status = "okay"; +}; + +&gpt1 { + status = "okay"; +}; + +&pwm0 { + status = "okay"; + pinctrl-0 = <&pwm0_default>; + pinctrl-names = "default"; +}; + +&pwm1 { + status = "okay"; + pinctrl-0 = <&pwm1_default>; + pinctrl-names = "default"; +}; From d939511dae6b29c0d65a066a5eafafdcb4a5524b Mon Sep 17 00:00:00 2001 From: Vdragon Date: Fri, 29 Nov 2024 08:58:21 +0100 Subject: [PATCH 047/154] zephyr: Create options to enable frozen modules. Enables the ability to use frozen modules in the zephyr port. Enabled by adding `CONFIG_MICROPY_FROZEN_MODULES` to the board configuration file. Manually set manifest path with `CONFIG_MICROPY_FROZEN_MANIFEST`. Signed-off-by: Vdragon --- ports/zephyr/CMakeLists.txt | 10 ++++++++-- ports/zephyr/Kconfig | 8 ++++++++ ports/zephyr/boards/manifest.py | 7 +++++++ 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 ports/zephyr/boards/manifest.py diff --git a/ports/zephyr/CMakeLists.txt b/ports/zephyr/CMakeLists.txt index b955089066749..4f457f4a58cb5 100644 --- a/ports/zephyr/CMakeLists.txt +++ b/ports/zephyr/CMakeLists.txt @@ -34,6 +34,11 @@ set(MICROPY_TARGET micropython) include(${MICROPY_DIR}/py/py.cmake) include(${MICROPY_DIR}/extmod/extmod.cmake) +if (CONFIG_MICROPY_FROZEN_MODULES) + cmake_path(ABSOLUTE_PATH CONFIG_MICROPY_FROZEN_MANIFEST BASE_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + set(MICROPY_FROZEN_MANIFEST ${CONFIG_MICROPY_FROZEN_MANIFEST}) +endif() + set(MICROPY_SOURCE_PORT main.c help.c @@ -115,10 +120,11 @@ zephyr_library_compile_definitions( zephyr_library_sources(${MICROPY_SOURCE_QSTR}) zephyr_library_link_libraries(kernel) -add_dependencies(${MICROPY_TARGET} zephyr_generated_headers) - include(${MICROPY_DIR}/py/mkrules.cmake) +add_dependencies(BUILD_VERSION_HEADER zephyr_generated_headers) +add_dependencies(${MICROPY_TARGET} zephyr_generated_headers) + target_sources(app PRIVATE src/zephyr_start.c src/zephyr_getchar.c diff --git a/ports/zephyr/Kconfig b/ports/zephyr/Kconfig index 227e943bcd13c..f8d154315597c 100644 --- a/ports/zephyr/Kconfig +++ b/ports/zephyr/Kconfig @@ -41,6 +41,14 @@ config MICROPY_VFS_LFS1 config MICROPY_VFS_LFS2 bool "LittleFs version 2 file system" +config MICROPY_FROZEN_MODULES + bool "Enable Frozen Modules" + +config MICROPY_FROZEN_MANIFEST + string "Path to Frozen Modules manifest.py" + depends on MICROPY_FROZEN_MODULES + default "boards/manifest.py" + endmenu # MicroPython Options source "Kconfig.zephyr" diff --git a/ports/zephyr/boards/manifest.py b/ports/zephyr/boards/manifest.py new file mode 100644 index 0000000000000..df1169c081c59 --- /dev/null +++ b/ports/zephyr/boards/manifest.py @@ -0,0 +1,7 @@ +# This is an example frozen module manifest. Enable this by configuring +# the Zephyr project and enabling the frozen modules config feature. + +freeze("$(PORT_DIR)/modules") + +# Require a micropython-lib module. +require("upysh") From e3d9d8ef51d3668ce51ab3cd7e610109a8f9db98 Mon Sep 17 00:00:00 2001 From: Patrick Joy Date: Sat, 8 Mar 2025 22:11:35 +1100 Subject: [PATCH 048/154] zephyr/boards: Add nrf5340dk board configuration. Add support for the nrf5340dk. This DK has a MX25R64 8mb external QSPI flash chip. Compile using: $ west build -b nrf5340dk/nrf5340/cpuapp Signed-off-by: Patrick Joy --- .../zephyr/boards/nrf5340dk_nrf5340_cpuapp.conf | 2 ++ .../boards/nrf5340dk_nrf5340_cpuapp.overlay | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 ports/zephyr/boards/nrf5340dk_nrf5340_cpuapp.conf create mode 100644 ports/zephyr/boards/nrf5340dk_nrf5340_cpuapp.overlay diff --git a/ports/zephyr/boards/nrf5340dk_nrf5340_cpuapp.conf b/ports/zephyr/boards/nrf5340dk_nrf5340_cpuapp.conf new file mode 100644 index 0000000000000..b4aed364a746e --- /dev/null +++ b/ports/zephyr/boards/nrf5340dk_nrf5340_cpuapp.conf @@ -0,0 +1,2 @@ +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y diff --git a/ports/zephyr/boards/nrf5340dk_nrf5340_cpuapp.overlay b/ports/zephyr/boards/nrf5340dk_nrf5340_cpuapp.overlay new file mode 100644 index 0000000000000..7310d1e55f760 --- /dev/null +++ b/ports/zephyr/boards/nrf5340dk_nrf5340_cpuapp.overlay @@ -0,0 +1,16 @@ +// Replace default internal storage partition with external flash + +/delete-node/ &storage_partition; + +&mx25r64 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + storage_partition: partition@0 { + reg = <0x00000000 0x800000>; + label = "storage"; + }; + }; +}; From 62479f2cb60ae73dcb81d0b0e2b2d147813e0604 Mon Sep 17 00:00:00 2001 From: Patrick Joy Date: Sat, 8 Mar 2025 23:55:53 +1100 Subject: [PATCH 049/154] zephyr/boards: Add nrf9151dk board configuration. Add support for the nrf9151dk. This DK has a GD25WB256 32mb external QSPI flash chip. Signed-off-by: Patrick Joy --- ports/zephyr/boards/nrf9151dk_nrf9151.conf | 7 ++++++ ports/zephyr/boards/nrf9151dk_nrf9151.overlay | 22 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 ports/zephyr/boards/nrf9151dk_nrf9151.conf create mode 100644 ports/zephyr/boards/nrf9151dk_nrf9151.overlay diff --git a/ports/zephyr/boards/nrf9151dk_nrf9151.conf b/ports/zephyr/boards/nrf9151dk_nrf9151.conf new file mode 100644 index 0000000000000..e89f332ba13d0 --- /dev/null +++ b/ports/zephyr/boards/nrf9151dk_nrf9151.conf @@ -0,0 +1,7 @@ +# Enable external flash +CONFIG_SPI=y +CONFIG_SPI_NOR=y +CONFIG_SPI_NOR_SFDP_DEVICETREE=y + +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y diff --git a/ports/zephyr/boards/nrf9151dk_nrf9151.overlay b/ports/zephyr/boards/nrf9151dk_nrf9151.overlay new file mode 100644 index 0000000000000..85cab574148fe --- /dev/null +++ b/ports/zephyr/boards/nrf9151dk_nrf9151.overlay @@ -0,0 +1,22 @@ +/ { + /* Configure partition manager to use gd25wb256 as the external flash */ + chosen { + nordic,pm-ext-flash = &gd25wb256; + }; +}; + +/delete-node/ &storage_partition; + +&gd25wb256 { + status = "okay"; + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + storage_partition: partition@0 { + reg = <0x00000000 0x2000000>; + label = "storage"; + }; + }; +}; From 3f1df4bacb2aa8f9018011c6a61eff9247248e1b Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 29 Apr 2025 10:24:52 +1000 Subject: [PATCH 050/154] tests/net_hosted: Only run network loopback test on supported targets. Only a few ports have TCP/IP loopback enabled in their network stack, and this test will only pass on those ports. There's not really any good way to do a feature check for loopback mode without actually running the test and seeing if it passes/fails, so add an explicit check that the test is running on a port known to support loopback. (Enabling loopback on lwIP, eg RPI_PICO_W, costs +568 code and +272 bss and is a rarely used feature, so not worth unconditionally enabling.) Signed-off-by: Damien George --- tests/net_hosted/asyncio_loopback.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/net_hosted/asyncio_loopback.py b/tests/net_hosted/asyncio_loopback.py index fd4674544ce71..03513ae6244d1 100644 --- a/tests/net_hosted/asyncio_loopback.py +++ b/tests/net_hosted/asyncio_loopback.py @@ -1,5 +1,12 @@ # Test network loopback behaviour +import sys + +# Only certain platforms can do TCP/IP loopback. +if sys.platform not in ("darwin", "esp32", "linux"): + print("SKIP") + raise SystemExit + try: import asyncio except ImportError: From ffd7e0e28d030225dcfdfc8af6fb6eb79fbdd56f Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 28 Apr 2025 13:19:39 +1000 Subject: [PATCH 051/154] tests/ports/rp2: Convert rp2.DMA test to a unittest. This test is rather complicated and benefits from being a unittest. Signed-off-by: Damien George --- tests/ports/rp2/rp2_dma.py | 233 +++++++++++++++++++-------------- tests/ports/rp2/rp2_dma.py.exp | 31 ----- 2 files changed, 136 insertions(+), 128 deletions(-) delete mode 100644 tests/ports/rp2/rp2_dma.py.exp diff --git a/tests/ports/rp2/rp2_dma.py b/tests/ports/rp2/rp2_dma.py index 62ec1dcf24d14..bd7f17913eae0 100644 --- a/tests/ports/rp2/rp2_dma.py +++ b/tests/ports/rp2/rp2_dma.py @@ -4,103 +4,142 @@ import time import machine import rp2 +import unittest is_rp2350 = "RP2350" in sys.implementation._machine -src = bytes(i & 0xFF for i in range(16 * 1024)) - -print("# test basic usage") - -dma = rp2.DMA() - -# Test printing. -print(dma) - -# Test pack_ctrl/unpack_ctrl. -ctrl_dict = rp2.DMA.unpack_ctrl(dma.pack_ctrl()) -if is_rp2350: - for entry in ("inc_read_rev", "inc_write_rev"): - assert entry in ctrl_dict - del ctrl_dict[entry] -for key, value in sorted(ctrl_dict.items()): - print(key, value) - -# Test register access. -dma.read = 0 -dma.write = 0 -dma.count = 0 -dma.ctrl = dma.pack_ctrl() -print(dma.read, dma.write, dma.count, dma.ctrl & 0x01F, dma.channel, dma.registers) -dma.close() - -# Test closing when already closed. -dma.close() - -# Test using when closed. -try: - dma.active() - assert False -except ValueError: - print("ValueError") - -# Test simple memory copy. -print("# test memory copy") -dest = bytearray(1024) -dma = rp2.DMA() -dma.config(read=src, write=dest, count=len(dest) // 4, ctrl=dma.pack_ctrl(), trigger=False) -print(not any(dest)) -dma.active(True) -while dma.active(): - pass -print(dest[:8], dest[-8:]) -dma.close() - - -# Test time taken for a large memory copy. -def run_and_time_dma(dma): - ticks_us = time.ticks_us - irq_state = machine.disable_irq() - t0 = ticks_us() - dma.active(True) - while dma.active(): - pass - t1 = ticks_us() - machine.enable_irq(irq_state) - return time.ticks_diff(t1, t0) - - -print("# test timing") -dest = bytearray(16 * 1024) -dma = rp2.DMA() -dma.read = src -dma.write = dest -dma.count = len(dest) // 4 -dma.ctrl = dma.pack_ctrl() -dt = run_and_time_dma(dma) -expected_dt_range = range(40, 70) if is_rp2350 else range(70, 125) -print(dt in expected_dt_range) -print(dest[:8], dest[-8:]) -dma.close() - -# Test using .config(trigger=True). -print("# test immediate trigger") -dest = bytearray(1024) -dma = rp2.DMA() -dma.config(read=src, write=dest, count=len(dest) // 4, ctrl=dma.pack_ctrl(), trigger=True) -while dma.active(): - pass -print(dest[:8], dest[-8:]) -dma.close() - -# Test the DMA.irq() method. -print("# test irq") -dest = bytearray(1024) -dma = rp2.DMA() -dma.irq(lambda dma: print("irq fired", dma.irq().flags())) -dma.config( - read=src, write=dest, count=len(dest) // 4, ctrl=dma.pack_ctrl(irq_quiet=0), trigger=True -) -while dma.active(): - pass -print(dest[:8], dest[-8:]) -dma.close() +SRC = bytes(i & 0xFF for i in range(16 * 1024)) + + +class Test(unittest.TestCase): + def setUp(self): + self.dma = rp2.DMA() + + def tearDown(self): + self.dma.close() + + def test_printing(self): + dma = self.dma + self.assertEqual(str(dma), "DMA(0)") + + def test_pack_unpack_ctrl(self): + dma = self.dma + ctrl_dict = rp2.DMA.unpack_ctrl(dma.pack_ctrl()) + if is_rp2350: + self.assertEqual(len(ctrl_dict), 18) + self.assertTrue("inc_read_rev" in ctrl_dict) + self.assertTrue("inc_write_rev" in ctrl_dict) + else: + self.assertEqual(len(ctrl_dict), 16) + self.assertEqual(ctrl_dict["ahb_err"], 0) + self.assertEqual(ctrl_dict["bswap"], 0) + self.assertEqual(ctrl_dict["busy"], 0) + self.assertEqual(ctrl_dict["chain_to"], 0) + self.assertEqual(ctrl_dict["enable"], 1) + self.assertEqual(ctrl_dict["high_pri"], 0) + self.assertEqual(ctrl_dict["inc_read"], 1) + self.assertEqual(ctrl_dict["inc_write"], 1) + self.assertEqual(ctrl_dict["irq_quiet"], 1) + self.assertEqual(ctrl_dict["read_err"], 0) + self.assertEqual(ctrl_dict["ring_sel"], 0) + self.assertEqual(ctrl_dict["ring_size"], 0) + self.assertEqual(ctrl_dict["size"], 2) + self.assertEqual(ctrl_dict["sniff_en"], 0) + self.assertEqual(ctrl_dict["treq_sel"], 63) + self.assertEqual(ctrl_dict["write_err"], 0) + + def test_register_access(self): + dma = self.dma + dma.read = 0 + dma.write = 0 + dma.count = 0 + dma.ctrl = dma.pack_ctrl() + self.assertEqual(dma.read, 0) + self.assertEqual(dma.write, 0) + self.assertEqual(dma.count, 0) + self.assertEqual(dma.ctrl & 0x01F, 25) + self.assertEqual(dma.channel, 0) + self.assertIsInstance(dma.registers, memoryview) + + def test_close(self): + dma = self.dma + dma.close() + + # Test closing when already closed. + dma.close() + + # Test using when closed. + with self.assertRaises(ValueError): + dma.active() + + def test_simple_memory_copy(self): + dma = self.dma + dest = bytearray(1024) + dma.config(read=SRC, write=dest, count=len(dest) // 4, ctrl=dma.pack_ctrl(), trigger=False) + self.assertFalse(any(dest)) + dma.active(True) + while dma.active(): + pass + self.assertEqual(dest[:8], SRC[:8]) + self.assertEqual(dest[-8:], SRC[-8:]) + + def test_time_taken_for_large_memory_copy(self): + def run_and_time_dma(dma): + ticks_us = time.ticks_us + irq_state = machine.disable_irq() + t0 = ticks_us() + dma.active(True) + while dma.active(): + pass + t1 = ticks_us() + machine.enable_irq(irq_state) + return time.ticks_diff(t1, t0) + + dma = self.dma + dest = bytearray(16 * 1024) + dma.read = SRC + dma.write = dest + dma.count = len(dest) // 4 + dma.ctrl = dma.pack_ctrl() + dt = run_and_time_dma(dma) + expected_dt_range = range(40, 70) if is_rp2350 else range(70, 125) + self.assertIn(dt, expected_dt_range) + self.assertEqual(dest[:8], SRC[:8]) + self.assertEqual(dest[-8:], SRC[-8:]) + + def test_config_trigger(self): + # Test using .config(trigger=True) to start DMA immediately. + dma = self.dma + dest = bytearray(1024) + dma.config(read=SRC, write=dest, count=len(dest) // 4, ctrl=dma.pack_ctrl(), trigger=True) + while dma.active(): + pass + self.assertEqual(dest[:8], SRC[:8]) + self.assertEqual(dest[-8:], SRC[-8:]) + + def test_irq(self): + def callback(dma): + nonlocal irq_flags + print("irq fired") + irq_flags = dma.irq().flags() + + dma = self.dma + irq_flags = None + dest = bytearray(1024) + dma.irq(callback) + dma.config( + read=SRC, + write=dest, + count=len(dest) // 4, + ctrl=dma.pack_ctrl(irq_quiet=0), + trigger=True, + ) + while dma.active(): + pass + self.assertEqual(irq_flags, 1) + self.assertEqual(dest[:8], SRC[:8]) + self.assertEqual(dest[-8:], SRC[-8:]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/ports/rp2/rp2_dma.py.exp b/tests/ports/rp2/rp2_dma.py.exp deleted file mode 100644 index 6fad5429b291d..0000000000000 --- a/tests/ports/rp2/rp2_dma.py.exp +++ /dev/null @@ -1,31 +0,0 @@ -# test basic usage -DMA(0) -ahb_err 0 -bswap 0 -busy 0 -chain_to 0 -enable 1 -high_pri 0 -inc_read 1 -inc_write 1 -irq_quiet 1 -read_err 0 -ring_sel 0 -ring_size 0 -size 2 -sniff_en 0 -treq_sel 63 -write_err 0 -0 0 0 25 0 -ValueError -# test memory copy -True -bytearray(b'\x00\x01\x02\x03\x04\x05\x06\x07') bytearray(b'\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff') -# test timing -True -bytearray(b'\x00\x01\x02\x03\x04\x05\x06\x07') bytearray(b'\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff') -# test immediate trigger -bytearray(b'\x00\x01\x02\x03\x04\x05\x06\x07') bytearray(b'\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff') -# test irq -irq fired 1 -bytearray(b'\x00\x01\x02\x03\x04\x05\x06\x07') bytearray(b'\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff') From 00a0cd70f54241d4c8fb04c396a636cb0d3a1535 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 28 Apr 2025 13:28:15 +1000 Subject: [PATCH 052/154] tests/ports/rp2: Tune rp2.DMA test so it runs in all configurations. Changes in this commit: - Allow the DMA instance to be any instance, not just DMA(0); eg WLAN may be using DMA(0). - Make the DMA timing test run a little faster by preloading `dma.active`. - Run the DMA timing test 10 times and take the average time taken as the test result, to eliminate any big effects of caching. - Change the expected time to `range(30, 80)` to cover RP2040, RP2350, RISC-V variants, and both bytecode and native emitter. - Add a `sleep_ms(1)` after waiting for the IRQ to fire, so that any scheduled code gets a chance to run when the test is compiled with the native emitter. With these changes this test passes reliably on RPI_PICO, RPI_PICO_W, RPI_PICO2, RPI_PICO2_W, RPI_PICO2-RISCV and RPI_PICO2_W-RISCV, in both bytecode and native emitter mode, with and without WLAN enabled. Signed-off-by: Damien George --- tests/ports/rp2/rp2_dma.py | 39 ++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/tests/ports/rp2/rp2_dma.py b/tests/ports/rp2/rp2_dma.py index bd7f17913eae0..436e5ee48ec5b 100644 --- a/tests/ports/rp2/rp2_dma.py +++ b/tests/ports/rp2/rp2_dma.py @@ -20,7 +20,7 @@ def tearDown(self): def test_printing(self): dma = self.dma - self.assertEqual(str(dma), "DMA(0)") + self.assertEqual(str(dma), "DMA({})".format(dma.channel)) def test_pack_unpack_ctrl(self): dma = self.dma @@ -34,7 +34,7 @@ def test_pack_unpack_ctrl(self): self.assertEqual(ctrl_dict["ahb_err"], 0) self.assertEqual(ctrl_dict["bswap"], 0) self.assertEqual(ctrl_dict["busy"], 0) - self.assertEqual(ctrl_dict["chain_to"], 0) + self.assertEqual(ctrl_dict["chain_to"], dma.channel) self.assertEqual(ctrl_dict["enable"], 1) self.assertEqual(ctrl_dict["high_pri"], 0) self.assertEqual(ctrl_dict["inc_read"], 1) @@ -58,7 +58,7 @@ def test_register_access(self): self.assertEqual(dma.write, 0) self.assertEqual(dma.count, 0) self.assertEqual(dma.ctrl & 0x01F, 25) - self.assertEqual(dma.channel, 0) + self.assertIn(dma.channel, range(16)) self.assertIsInstance(dma.registers, memoryview) def test_close(self): @@ -86,26 +86,32 @@ def test_simple_memory_copy(self): def test_time_taken_for_large_memory_copy(self): def run_and_time_dma(dma): ticks_us = time.ticks_us + active = dma.active irq_state = machine.disable_irq() t0 = ticks_us() - dma.active(True) - while dma.active(): + active(True) + while active(): pass t1 = ticks_us() machine.enable_irq(irq_state) return time.ticks_diff(t1, t0) - dma = self.dma - dest = bytearray(16 * 1024) - dma.read = SRC - dma.write = dest - dma.count = len(dest) // 4 - dma.ctrl = dma.pack_ctrl() - dt = run_and_time_dma(dma) - expected_dt_range = range(40, 70) if is_rp2350 else range(70, 125) - self.assertIn(dt, expected_dt_range) - self.assertEqual(dest[:8], SRC[:8]) - self.assertEqual(dest[-8:], SRC[-8:]) + num_average = 10 + dt_sum = 0 + for _ in range(num_average): + dma = self.dma + dest = bytearray(16 * 1024) + dma.read = SRC + dma.write = dest + dma.count = len(dest) // 4 + dma.ctrl = dma.pack_ctrl() + dt_sum += run_and_time_dma(dma) + self.assertEqual(dest[:8], SRC[:8]) + self.assertEqual(dest[-8:], SRC[-8:]) + self.tearDown() + self.setUp() + dt = dt_sum // num_average + self.assertIn(dt, range(30, 80)) def test_config_trigger(self): # Test using .config(trigger=True) to start DMA immediately. @@ -136,6 +142,7 @@ def callback(dma): ) while dma.active(): pass + time.sleep_ms(1) # when running as native code, give the scheduler a chance to run self.assertEqual(irq_flags, 1) self.assertEqual(dest[:8], SRC[:8]) self.assertEqual(dest[-8:], SRC[-8:]) From 4117a2d9b5a2e0f3e84e7d49a8f05ae04fac8b39 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 30 Apr 2025 11:54:01 +1000 Subject: [PATCH 053/154] tools/ci.sh: Update URL for xtensa-lx106-elf-standalone.tar.gz. The https://github.com/jepler/esp-open-sdk repository has been removed, so use the file hosted at micropython.org (it's the same file). Signed-off-by: Damien George --- tools/ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci.sh b/tools/ci.sh index 6f8d1cb80c4aa..a4bd43567c861 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -209,7 +209,7 @@ function ci_esp32_build_s3_c3 { function ci_esp8266_setup { sudo pip3 install pyserial esptool==3.3.1 pyelftools ar - wget https://github.com/jepler/esp-open-sdk/releases/download/2018-06-10/xtensa-lx106-elf-standalone.tar.gz + wget https://micropython.org/resources/xtensa-lx106-elf-standalone.tar.gz zcat xtensa-lx106-elf-standalone.tar.gz | tar x # Remove this esptool.py so pip version is used instead rm xtensa-lx106-elf/bin/esptool.py From e53f262a85349b4871a38d899a30b05b2ed4b62f Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 10 Apr 2025 16:00:21 +1000 Subject: [PATCH 054/154] tools/mpremote: For mip install, use hash to skip files that exist. When using `mip install`, if a file that needs to be downloaded already exists locally, then the hash of that local file will be computed and if it matches the known hash of the remote file it will not be downloaded. Hashes in mip are guaranteed unique, so this change should never leave stale files on the filesystem. This behaviour follows that of the `mip` package in `micropython-lib`. Signed-off-by: Damien George --- tools/mpremote/mpremote/mip.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tools/mpremote/mpremote/mip.py b/tools/mpremote/mpremote/mip.py index 26ae8bec5ec6c..fa7974053f44d 100644 --- a/tools/mpremote/mpremote/mip.py +++ b/tools/mpremote/mpremote/mip.py @@ -34,6 +34,15 @@ def _ensure_path_exists(transport, path): prefix += "/" +# Check if the specified path exists and matches the hash. +def _check_exists(transport, path, short_hash): + try: + remote_hash = transport.fs_hashfile(path, "sha256") + except FileNotFoundError: + return False + return remote_hash.hex()[: len(short_hash)] == short_hash + + def _rewrite_url(url, branch=None): if not branch: branch = "HEAD" @@ -115,8 +124,11 @@ def _install_json(transport, package_json_url, index, target, version, mpy): raise CommandError(f"Invalid url for package: {package_json_url}") for target_path, short_hash in package_json.get("hashes", ()): fs_target_path = target + "/" + target_path - file_url = f"{index}/file/{short_hash[:2]}/{short_hash}" - _download_file(transport, file_url, fs_target_path) + if _check_exists(transport, fs_target_path, short_hash): + print("Exists:", fs_target_path) + else: + file_url = f"{index}/file/{short_hash[:2]}/{short_hash}" + _download_file(transport, file_url, fs_target_path) for target_path, url in package_json.get("urls", ()): fs_target_path = target + "/" + target_path if base_url and not url.startswith(allowed_mip_url_prefixes): From 6601d4d7ebd9c6ce11a28f63efa0ec0a6a2fa27a Mon Sep 17 00:00:00 2001 From: Vdragon Date: Fri, 29 Nov 2024 09:37:31 +0100 Subject: [PATCH 055/154] zephyr: Create ability to use device_next with CDC ACM as REPL. This enables using the newer USB stack and its CDC ACM for the REPL. To switch to it, board file must contain `CONFIG_USB_DEVICE_STACK_NEXT=y` and `CONFIG_USBD_CDC_ACM_CLASS=y`. In the case of a board that is a platform that supports the older device stack, `CONFIG_USB_DEVICE_STACK=n` may be necessary. Signed-off-by: Vdragon --- ports/zephyr/CMakeLists.txt | 1 + ports/zephyr/Kconfig | 8 ++ ports/zephyr/main.c | 8 ++ ports/zephyr/src/usbd.c | 183 ++++++++++++++++++++++++++++++++++++ 4 files changed, 200 insertions(+) create mode 100644 ports/zephyr/src/usbd.c diff --git a/ports/zephyr/CMakeLists.txt b/ports/zephyr/CMakeLists.txt index 4f457f4a58cb5..debf2bd2c15d4 100644 --- a/ports/zephyr/CMakeLists.txt +++ b/ports/zephyr/CMakeLists.txt @@ -128,6 +128,7 @@ add_dependencies(${MICROPY_TARGET} zephyr_generated_headers) target_sources(app PRIVATE src/zephyr_start.c src/zephyr_getchar.c + src/usbd.c ) target_link_libraries(app PRIVATE ${MICROPY_TARGET}) diff --git a/ports/zephyr/Kconfig b/ports/zephyr/Kconfig index f8d154315597c..6db0133f4f15d 100644 --- a/ports/zephyr/Kconfig +++ b/ports/zephyr/Kconfig @@ -49,6 +49,14 @@ config MICROPY_FROZEN_MANIFEST depends on MICROPY_FROZEN_MODULES default "boards/manifest.py" +config MICROPY_USB_DEVICE_VID + hex "USB VID" + default 0x2fe3 + +config MICROPY_USB_DEVICE_PID + hex "USB PID" + default 0x0001 + endmenu # MicroPython Options source "Kconfig.zephyr" diff --git a/ports/zephyr/main.c b/ports/zephyr/main.c index 206b7f92d3917..d4498c1079c1d 100644 --- a/ports/zephyr/main.c +++ b/ports/zephyr/main.c @@ -63,6 +63,10 @@ static char heap[MICROPY_HEAP_SIZE]; +#if defined(CONFIG_USB_DEVICE_STACK_NEXT) +extern int mp_usbd_init(void); +#endif // defined(CONFIG_USB_DEVICE_STACK_NEXT) + void init_zephyr(void) { // We now rely on CONFIG_NET_APP_SETTINGS to set up bootstrap // network addresses. @@ -143,6 +147,10 @@ int real_main(void) { usb_enable(NULL); #endif + #ifdef CONFIG_USB_DEVICE_STACK_NEXT + mp_usbd_init(); + #endif + #if MICROPY_VFS vfs_init(); #endif diff --git a/ports/zephyr/src/usbd.c b/ports/zephyr/src/usbd.c new file mode 100644 index 0000000000000..2444706cbeaa9 --- /dev/null +++ b/ports/zephyr/src/usbd.c @@ -0,0 +1,183 @@ +/* +* This file is part of the MicroPython project, http://micropython.org/ +* +* The MIT License (MIT) +* +* Copyright (c) 2024-2025 MASSDRIVER EI (massdriver.space) +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +*/ +#include + +#include +#include +#include + +#if defined(CONFIG_USB_DEVICE_STACK_NEXT) + +#include +LOG_MODULE_REGISTER(mp_usbd); + +/* By default, do not register the USB DFU class DFU mode instance. */ +static const char *const blocklist[] = { + "dfu_dfu", + NULL, +}; + +USBD_DEVICE_DEFINE(mp_usbd, + DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)), + CONFIG_MICROPY_USB_DEVICE_VID, CONFIG_MICROPY_USB_DEVICE_PID); + +USBD_DESC_LANG_DEFINE(mp_lang); +USBD_DESC_MANUFACTURER_DEFINE(mp_mfr, "Zephyr Project"); +USBD_DESC_PRODUCT_DEFINE(mp_product, "Micropython on Zephyr RTOS"); +USBD_DESC_SERIAL_NUMBER_DEFINE(mp_sn); + +USBD_DESC_CONFIG_DEFINE(fs_cfg_desc, "FS Configuration"); +USBD_DESC_CONFIG_DEFINE(hs_cfg_desc, "HS Configuration"); + +/* not self-powered, no remote wakeup */ +static const uint8_t attributes = 0; + +/* Full speed configuration +* power = 250 * 2 mA = 500mA +*/ +USBD_CONFIGURATION_DEFINE(mp_fs_config, + attributes, + 250, &fs_cfg_desc); + +/* High speed configuration */ +USBD_CONFIGURATION_DEFINE(mp_hs_config, + attributes, + 250, &hs_cfg_desc); + +static void mp_fix_code_triple(struct usbd_context *uds_ctx, + const enum usbd_speed speed) { + /* Always use class code information from Interface Descriptors */ + if (IS_ENABLED(CONFIG_USBD_CDC_ACM_CLASS) || + IS_ENABLED(CONFIG_USBD_CDC_ECM_CLASS) || + IS_ENABLED(CONFIG_USBD_CDC_NCM_CLASS) || + IS_ENABLED(CONFIG_USBD_AUDIO2_CLASS)) { + /* + * Class with multiple interfaces have an Interface + * Association Descriptor available, use an appropriate triple + * to indicate it. + */ + usbd_device_set_code_triple(uds_ctx, speed, + USB_BCC_MISCELLANEOUS, 0x02, 0x01); + } else { + usbd_device_set_code_triple(uds_ctx, speed, 0, 0, 0); + } +} + +struct usbd_context *mp_usbd_init_device(usbd_msg_cb_t msg_cb) { + int err; + + err = usbd_add_descriptor(&mp_usbd, &mp_lang); + if (err) { + LOG_ERR("Failed to initialize language descriptor (%d)", err); + return NULL; + } + + err = usbd_add_descriptor(&mp_usbd, &mp_mfr); + if (err) { + LOG_ERR("Failed to initialize manufacturer descriptor (%d)", err); + return NULL; + } + + err = usbd_add_descriptor(&mp_usbd, &mp_product); + if (err) { + LOG_ERR("Failed to initialize product descriptor (%d)", err); + return NULL; + } + + err = usbd_add_descriptor(&mp_usbd, &mp_sn); + if (err) { + LOG_ERR("Failed to initialize SN descriptor (%d)", err); + return NULL; + } + + if (usbd_caps_speed(&mp_usbd) == USBD_SPEED_HS) { + err = usbd_add_configuration(&mp_usbd, USBD_SPEED_HS, + &mp_hs_config); + if (err) { + LOG_ERR("Failed to add High-Speed configuration"); + return NULL; + } + + err = usbd_register_all_classes(&mp_usbd, USBD_SPEED_HS, 1, blocklist); + if (err) { + LOG_ERR("Failed to add register classes"); + return NULL; + } + + mp_fix_code_triple(&mp_usbd, USBD_SPEED_HS); + } + + err = usbd_add_configuration(&mp_usbd, USBD_SPEED_FS, + &mp_fs_config); + if (err) { + LOG_ERR("Failed to add Full-Speed configuration"); + return NULL; + } + + err = usbd_register_all_classes(&mp_usbd, USBD_SPEED_FS, 1, blocklist); + if (err) { + LOG_ERR("Failed to add register classes"); + return NULL; + } + + mp_fix_code_triple(&mp_usbd, USBD_SPEED_FS); + + if (msg_cb != NULL) { + err = usbd_msg_register_cb(&mp_usbd, msg_cb); + if (err) { + LOG_ERR("Failed to register message callback"); + return NULL; + } + } + + err = usbd_init(&mp_usbd); + if (err) { + LOG_ERR("Failed to initialize device support"); + return NULL; + } + + return &mp_usbd; +} + +static struct usbd_context *mp_usbd_context; + +int mp_usbd_init(void) { + int err; + + mp_usbd_context = mp_usbd_init_device(NULL); + if (mp_usbd_context == NULL) { + return -ENODEV; + } + + err = usbd_enable(mp_usbd_context); + if (err) { + return err; + } + + return 0; +} + +#endif // defined(CONFIG_USB_DEVICE_STACK_NEXT) From 8728db3e414bdddebed5f9439b971a40c62eab9b Mon Sep 17 00:00:00 2001 From: Vdragon Date: Fri, 7 Mar 2025 14:17:42 +0100 Subject: [PATCH 056/154] zephyr: Introduce auto-listing of FlashArea Partitions. This enables listing all flash area partitions automagically instead of just sotrage_partitions. It uses the label, and the ID when not present. Signed-off-by: Vdragon --- ports/zephyr/zephyr_storage.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/ports/zephyr/zephyr_storage.c b/ports/zephyr/zephyr_storage.c index 484feb113017a..40bcef73384cf 100644 --- a/ports/zephyr/zephyr_storage.c +++ b/ports/zephyr/zephyr_storage.c @@ -139,6 +139,20 @@ MP_DEFINE_CONST_OBJ_TYPE( #endif // CONFIG_DISK_ACCESS #ifdef CONFIG_FLASH_MAP + +#define FLASH_AREA_DEFINE_LABEL(part) CONCAT(MP_QSTR_ID_, DT_STRING_TOKEN(part, label)) +#define FLASH_AREA_DEFINE_NB(part) CONCAT(MP_QSTR_ID_, DT_FIXED_PARTITION_ID(part)) + +#define FLASH_AREA_DEFINE_GETNAME(part) COND_CODE_1(DT_NODE_HAS_PROP(part, label), \ + (FLASH_AREA_DEFINE_LABEL(part)), (FLASH_AREA_DEFINE_NB(part))) + +#define FLASH_AREA_DEFINE_DEFINE(part) { MP_ROM_QSTR(FLASH_AREA_DEFINE_GETNAME(part)), MP_ROM_INT(DT_FIXED_PARTITION_ID(part)) }, + +#define FLASH_AREA_DEFINE(part) COND_CODE_1(DT_NODE_HAS_STATUS_OKAY(DT_MTD_FROM_FIXED_PARTITION(part)), \ + (FLASH_AREA_DEFINE_DEFINE(part)), ()) + +#define FOREACH_PARTITION(n) DT_FOREACH_CHILD(n, FLASH_AREA_DEFINE) + const mp_obj_type_t zephyr_flash_area_type; typedef struct _zephyr_flash_area_obj_t { @@ -244,9 +258,8 @@ static const mp_rom_map_elem_t zephyr_flash_area_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&zephyr_flash_area_readblocks_obj) }, { MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&zephyr_flash_area_writeblocks_obj) }, { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&zephyr_flash_area_ioctl_obj) }, - #if FIXED_PARTITION_EXISTS(storage_partition) - { MP_ROM_QSTR(MP_QSTR_STORAGE), MP_ROM_INT(FIXED_PARTITION_ID(storage_partition)) }, - #endif + /* Generate list of partition IDs from Zephyr Devicetree */ + DT_FOREACH_STATUS_OKAY(fixed_partitions, FOREACH_PARTITION) }; static MP_DEFINE_CONST_DICT(zephyr_flash_area_locals_dict, zephyr_flash_area_locals_dict_table); From ced7ebb873f923397aa5c9ddc7b286e32eb4e80a Mon Sep 17 00:00:00 2001 From: Vdragon Date: Wed, 30 Apr 2025 15:40:26 +0200 Subject: [PATCH 057/154] docs/zephyr: Add zephyr FlashArea IDs docs. Signed-off-by: Vdragon --- docs/zephyr/quickref.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/zephyr/quickref.rst b/docs/zephyr/quickref.rst index a7ae69860772d..0e985c70b3eb6 100644 --- a/docs/zephyr/quickref.rst +++ b/docs/zephyr/quickref.rst @@ -153,6 +153,8 @@ Use the :ref:`zephyr.FlashArea ` class to support filesystem:: f.write('Hello world') # write to the file print(open('/flash/hello.txt').read()) # print contents of the file +The FlashAreas' IDs that are available are listed in the FlashArea module, as ID_*. + Sensor ------ From bee1fd5e7887d48ad2b217f5c4746f6b518f3fd8 Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Wed, 16 Oct 2024 17:08:49 +0530 Subject: [PATCH 058/154] zephyr/boards: Enable ADC on beagleconnect_freedom. Enable Analog inputs. Requires Zephyr >= v3.8.0. Signed-off-by: Ayush Singh --- ports/zephyr/boards/beagleconnect_freedom.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/zephyr/boards/beagleconnect_freedom.conf b/ports/zephyr/boards/beagleconnect_freedom.conf index 1e3f6037bd8da..8fe2583205b39 100644 --- a/ports/zephyr/boards/beagleconnect_freedom.conf +++ b/ports/zephyr/boards/beagleconnect_freedom.conf @@ -1,4 +1,5 @@ # Hardware features +CONFIG_ADC=y CONFIG_PWM=y CONFIG_I2C=y CONFIG_SPI=y From 70ed3151933635429a66937bae2701958b6b47dd Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 24 Oct 2024 13:28:37 +1100 Subject: [PATCH 059/154] py/malloc: Add mutex for tracked allocations. Fixes thread safety issue that could cause memory corruption on ports with (MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL) - currently only rp2 and unix have this configuration. Adds unit test for TLS sockets that exercises this code path. I wasn't able to make this fail on rp2, the race condition window is pretty narrow and may not have a direct impact on a quiet system. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- py/malloc.c | 33 ++++++++++++++++++++- tests/extmod/ssl_threads.py | 57 +++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 tests/extmod/ssl_threads.py diff --git a/py/malloc.c b/py/malloc.c index f557ade44f500..05daeb35d098a 100644 --- a/py/malloc.c +++ b/py/malloc.c @@ -209,6 +209,31 @@ void m_free(void *ptr) #if MICROPY_TRACKED_ALLOC +#if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL +// If there's no GIL, use the GC recursive mutex to protect the tracked node linked list +// under m_tracked_head. +// +// (For ports with GIL, the expectation is to only call tracked alloc functions +// while holding the GIL.) + +static inline void m_tracked_node_lock(void) { + mp_thread_recursive_mutex_lock(&MP_STATE_MEM(gc_mutex), 1); +} + +static inline void m_tracked_node_unlock(void) { + mp_thread_recursive_mutex_unlock(&MP_STATE_MEM(gc_mutex)); +} + +#else + +static inline void m_tracked_node_lock(void) { +} + +static inline void m_tracked_node_unlock(void) { +} + +#endif + #define MICROPY_TRACKED_ALLOC_STORE_SIZE (!MICROPY_ENABLE_GC) typedef struct _m_tracked_node_t { @@ -222,6 +247,7 @@ typedef struct _m_tracked_node_t { #if MICROPY_DEBUG_VERBOSE static size_t m_tracked_count_links(size_t *nb) { + m_tracked_node_lock(); m_tracked_node_t *node = MP_STATE_VM(m_tracked_head); size_t n = 0; *nb = 0; @@ -234,6 +260,7 @@ static size_t m_tracked_count_links(size_t *nb) { #endif node = node->next; } + m_tracked_node_unlock(); return n; } #endif @@ -248,12 +275,14 @@ void *m_tracked_calloc(size_t nmemb, size_t size) { size_t n = m_tracked_count_links(&nb); DEBUG_printf("m_tracked_calloc(%u, %u) -> (%u;%u) %p\n", (int)nmemb, (int)size, (int)n, (int)nb, node); #endif + m_tracked_node_lock(); if (MP_STATE_VM(m_tracked_head) != NULL) { MP_STATE_VM(m_tracked_head)->prev = node; } node->prev = NULL; node->next = MP_STATE_VM(m_tracked_head); MP_STATE_VM(m_tracked_head) = node; + m_tracked_node_unlock(); #if MICROPY_TRACKED_ALLOC_STORE_SIZE node->size = nmemb * size; #endif @@ -278,7 +307,8 @@ void m_tracked_free(void *ptr_in) { size_t nb; size_t n = m_tracked_count_links(&nb); DEBUG_printf("m_tracked_free(%p, [%p, %p], nbytes=%u, links=%u;%u)\n", node, node->prev, node->next, (int)data_bytes, (int)n, (int)nb); - #endif + #endif // MICROPY_DEBUG_VERBOSE + m_tracked_node_lock(); if (node->next != NULL) { node->next->prev = node->prev; } @@ -287,6 +317,7 @@ void m_tracked_free(void *ptr_in) { } else { MP_STATE_VM(m_tracked_head) = node->next; } + m_tracked_node_unlock(); m_free(node #if MICROPY_MALLOC_USES_ALLOCATED_SIZE #if MICROPY_TRACKED_ALLOC_STORE_SIZE diff --git a/tests/extmod/ssl_threads.py b/tests/extmod/ssl_threads.py new file mode 100644 index 0000000000000..4564abd3d80de --- /dev/null +++ b/tests/extmod/ssl_threads.py @@ -0,0 +1,57 @@ +# Ensure that SSL sockets can be allocated from multiple +# threads without thread safety issues +import unittest + +try: + import _thread + import io + import tls + import time +except ImportError: + print("SKIP") + raise SystemExit + + +class TestSocket(io.IOBase): + def write(self, buf): + return len(buf) + + def readinto(self, buf): + return 0 + + def ioctl(self, cmd, arg): + return 0 + + def setblocking(self, value): + pass + + +ITERS = 256 + + +class TLSThreads(unittest.TestCase): + def test_sslsocket_threaded(self): + self.done = False + # only run in two threads: too much RAM demand otherwise, and rp2 only + # supports two anyhow + _thread.start_new_thread(self._alloc_many_sockets, (True,)) + self._alloc_many_sockets(False) + while not self.done: + time.sleep(0.1) + print("done") + + def _alloc_many_sockets(self, set_done_flag): + print("start", _thread.get_ident()) + ctx = tls.SSLContext(tls.PROTOCOL_TLS_CLIENT) + ctx.verify_mode = tls.CERT_NONE + for n in range(ITERS): + s = TestSocket() + s = ctx.wrap_socket(s, do_handshake_on_connect=False) + s.close() # Free associated resources now from thread, not in a GC pass + print("done", _thread.get_ident()) + if set_done_flag: + self.done = True + + +if __name__ == "__main__": + unittest.main() From 79abdad9e97f18f45650e1abce64ee51c3372953 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 1 May 2025 16:24:13 +1000 Subject: [PATCH 060/154] tests/extmod: Rename ssl tests that only use the tls module. Signed-off-by: Angus Gratton --- tests/extmod/{ssl_noleak.py => tls_noleak.py} | 0 tests/extmod/{ssl_threads.py => tls_threads.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/extmod/{ssl_noleak.py => tls_noleak.py} (100%) rename tests/extmod/{ssl_threads.py => tls_threads.py} (100%) diff --git a/tests/extmod/ssl_noleak.py b/tests/extmod/tls_noleak.py similarity index 100% rename from tests/extmod/ssl_noleak.py rename to tests/extmod/tls_noleak.py diff --git a/tests/extmod/ssl_threads.py b/tests/extmod/tls_threads.py similarity index 100% rename from tests/extmod/ssl_threads.py rename to tests/extmod/tls_threads.py From bdb7e036d2400c25beeb2b4252ff51c5255c4020 Mon Sep 17 00:00:00 2001 From: Yoctopuce dev Date: Sat, 15 Feb 2025 15:24:50 +0100 Subject: [PATCH 061/154] extmod/asyncio: Fix early exit of asyncio scheduler. This commit fixes three open issues related to the asyncio scheduler exiting prematurely when the main task queue is empty, in cases where CPython would not exit (for example, because the main task is not done because it's on a different queue). In the first case, the scheduler exits because running a task via `run_until_complete` did not schedule any dependent tasks. In the other two cases, the scheduler exits because the tasks are queued in an event queue. Tests have been added which reproduce the original issues. These test cases document the unauthorized use of `Event.set()` from a soft IRQ, and are skipped in unsupported environments (webassembly and native emitter). Fixes issues #16759, #16569 and #16318. Signed-off-by: Yoctopuce dev --- extmod/asyncio/core.py | 36 +++++--- tests/extmod/asyncio_event_queue.py | 64 +++++++++++++++ tests/extmod/asyncio_event_queue.py.exp | 5 ++ tests/extmod/asyncio_iterator_event.py | 86 ++++++++++++++++++++ tests/extmod/asyncio_iterator_event.py.exp | 5 ++ tests/extmod/asyncio_wait_for_linked_task.py | 66 +++++++++++++++ tests/run-tests.py | 5 ++ 7 files changed, 255 insertions(+), 12 deletions(-) create mode 100644 tests/extmod/asyncio_event_queue.py create mode 100644 tests/extmod/asyncio_event_queue.py.exp create mode 100644 tests/extmod/asyncio_iterator_event.py create mode 100644 tests/extmod/asyncio_iterator_event.py.exp create mode 100644 tests/extmod/asyncio_wait_for_linked_task.py diff --git a/extmod/asyncio/core.py b/extmod/asyncio/core.py index 8aad234514b0c..5d46b4b80e2a1 100644 --- a/extmod/asyncio/core.py +++ b/extmod/asyncio/core.py @@ -163,9 +163,16 @@ def run_until_complete(main_task=None): # A task waiting on _task_queue; "ph_key" is time to schedule task at dt = max(0, ticks_diff(t.ph_key, ticks())) elif not _io_queue.map: - # No tasks can be woken so finished running + # No tasks can be woken cur_task = None - return + if not main_task or not main_task.state: + # no main_task, or main_task is done so finished running + return + # At this point, there is theoretically nothing that could wake the + # scheduler, but it is not allowed to exit either. We keep the code + # running so that a hypothetical debugger (or other such meta-process) + # can get a view of what is happening and possibly abort. + dt = 3 # print('(poll {})'.format(dt), len(_io_queue.map)) _io_queue.wait_io_event(dt) @@ -187,31 +194,33 @@ def run_until_complete(main_task=None): except excs_all as er: # Check the task is not on any event queue assert t.data is None - # This task is done, check if it's the main task and then loop should stop - if t is main_task: + # If it's the main task, it is considered as awaited by the caller + awaited = t is main_task + if awaited: cur_task = None - if isinstance(er, StopIteration): - return er.value - raise er + if not isinstance(er, StopIteration): + t.state = False + raise er + if t.state is None: + t.state = False if t.state: # Task was running but is now finished. - waiting = False if t.state is True: # "None" indicates that the task is complete and not await'ed on (yet). - t.state = None + t.state = False if awaited else None elif callable(t.state): # The task has a callback registered to be called on completion. t.state(t, er) t.state = False - waiting = True + awaited = True else: # Schedule any other tasks waiting on the completion of this task. while t.state.peek(): _task_queue.push(t.state.pop()) - waiting = True + awaited = True # "False" indicates that the task is complete and has been await'ed on. t.state = False - if not waiting and not isinstance(er, excs_stop): + if not awaited and not isinstance(er, excs_stop): # An exception ended this detached task, so queue it for later # execution to handle the uncaught exception if no other task retrieves # the exception in the meantime (this is handled by Task.throw). @@ -229,6 +238,9 @@ def run_until_complete(main_task=None): _exc_context["exception"] = exc _exc_context["future"] = t Loop.call_exception_handler(_exc_context) + # If it's the main task then the loop should stop + if t is main_task: + return er.value # Create a new task from a coroutine and run it until it finishes diff --git a/tests/extmod/asyncio_event_queue.py b/tests/extmod/asyncio_event_queue.py new file mode 100644 index 0000000000000..e0125b1aefe13 --- /dev/null +++ b/tests/extmod/asyncio_event_queue.py @@ -0,0 +1,64 @@ +# Ensure that an asyncio task can wait on an Event when the +# _task_queue is empty +# https://github.com/micropython/micropython/issues/16569 + +try: + import asyncio +except ImportError: + print("SKIP") + raise SystemExit + +# This test requires checking that the asyncio scheduler +# remains active "indefinitely" when the task queue is empty. +# +# To check this, we need another independent scheduler that +# can wait for a certain amount of time. So we have to +# create one using micropython.schedule() and time.ticks_ms() +# +# Technically, this code breaks the rules, as it is clearly +# documented that Event.set() should _NOT_ be called from a +# schedule (soft IRQ) because in some cases, a race condition +# can occur, resulting in a crash. However: +# - since the risk of a race condition in that specific +# case has been analysed and excluded +# - given that there is no other simple alternative to +# write this test case, +# an exception to the rule was deemed acceptable. See +# https://github.com/micropython/micropython/pull/16772 + +import micropython, time + +try: + micropython.schedule +except AttributeError: + print("SKIP") + raise SystemExit + + +evt = asyncio.Event() + + +def schedule_watchdog(end_ticks): + if time.ticks_diff(end_ticks, time.ticks_ms()) <= 0: + print("asyncio still pending, unlocking event") + # Caution: about to call Event.set() from a schedule + # (see the note in the comment above) + evt.set() + return + micropython.schedule(schedule_watchdog, end_ticks) + + +async def foo(): + print("foo waiting") + schedule_watchdog(time.ticks_add(time.ticks_ms(), 100)) + await evt.wait() + print("foo done") + + +async def main(): + print("main started") + await foo() + print("main done") + + +asyncio.run(main()) diff --git a/tests/extmod/asyncio_event_queue.py.exp b/tests/extmod/asyncio_event_queue.py.exp new file mode 100644 index 0000000000000..ee42c96d83ed8 --- /dev/null +++ b/tests/extmod/asyncio_event_queue.py.exp @@ -0,0 +1,5 @@ +main started +foo waiting +asyncio still pending, unlocking event +foo done +main done diff --git a/tests/extmod/asyncio_iterator_event.py b/tests/extmod/asyncio_iterator_event.py new file mode 100644 index 0000000000000..6efa6b864567a --- /dev/null +++ b/tests/extmod/asyncio_iterator_event.py @@ -0,0 +1,86 @@ +# Ensure that an asyncio task can wait on an Event when the +# _task_queue is empty, in the context of an async iterator +# https://github.com/micropython/micropython/issues/16318 + +try: + import asyncio +except ImportError: + print("SKIP") + raise SystemExit + +# This test requires checking that the asyncio scheduler +# remains active "indefinitely" when the task queue is empty. +# +# To check this, we need another independent scheduler that +# can wait for a certain amount of time. So we have to +# create one using micropython.schedule() and time.ticks_ms() +# +# Technically, this code breaks the rules, as it is clearly +# documented that Event.set() should _NOT_ be called from a +# schedule (soft IRQ) because in some cases, a race condition +# can occur, resulting in a crash. However: +# - since the risk of a race condition in that specific +# case has been analysed and excluded +# - given that there is no other simple alternative to +# write this test case, +# an exception to the rule was deemed acceptable. See +# https://github.com/micropython/micropython/pull/16772 + +import micropython, time + +try: + micropython.schedule +except AttributeError: + print("SKIP") + raise SystemExit + +ai = None + + +def schedule_watchdog(end_ticks): + if time.ticks_diff(end_ticks, time.ticks_ms()) <= 0: + print("good: asyncio iterator is still pending, exiting") + # Caution: ai.fetch_data() will invoke Event.set() + # (see the note in the comment above) + ai.fetch_data(None) + return + micropython.schedule(schedule_watchdog, end_ticks) + + +async def test(ai): + for x in range(3): + await asyncio.sleep(0.1) + ai.fetch_data(f"bar {x}") + + +class AsyncIterable: + def __init__(self): + self.message = None + self.evt = asyncio.Event() + + def __aiter__(self): + return self + + async def __anext__(self): + await self.evt.wait() + self.evt.clear() + if self.message is None: + raise StopAsyncIteration + return self.message + + def fetch_data(self, message): + self.message = message + self.evt.set() + + +async def main(): + global ai + ai = AsyncIterable() + asyncio.create_task(test(ai)) + schedule_watchdog(time.ticks_add(time.ticks_ms(), 500)) + async for message in ai: + print(message) + print("end main") + + +asyncio.run(main()) diff --git a/tests/extmod/asyncio_iterator_event.py.exp b/tests/extmod/asyncio_iterator_event.py.exp new file mode 100644 index 0000000000000..a1893197d02ef --- /dev/null +++ b/tests/extmod/asyncio_iterator_event.py.exp @@ -0,0 +1,5 @@ +bar 0 +bar 1 +bar 2 +good: asyncio iterator is still pending, exiting +end main diff --git a/tests/extmod/asyncio_wait_for_linked_task.py b/tests/extmod/asyncio_wait_for_linked_task.py new file mode 100644 index 0000000000000..4dda62d5476c7 --- /dev/null +++ b/tests/extmod/asyncio_wait_for_linked_task.py @@ -0,0 +1,66 @@ +# Test asyncio.wait_for, with dependent tasks +# https://github.com/micropython/micropython/issues/16759 + +try: + import asyncio +except ImportError: + print("SKIP") + raise SystemExit + + +# CPython 3.12 deprecated calling get_event_loop() when there is no current event +# loop, so to make this test run on CPython requires setting the event loop. +if hasattr(asyncio, "set_event_loop"): + asyncio.set_event_loop(asyncio.new_event_loop()) + + +class Worker: + def __init__(self): + self._eventLoop = None + self._tasks = [] + + def launchTask(self, asyncJob): + if self._eventLoop is None: + self._eventLoop = asyncio.get_event_loop() + return self._eventLoop.create_task(asyncJob) + + async def job(self, prerequisite, taskName): + if prerequisite: + await prerequisite + await asyncio.sleep(0.1) + print(taskName, "work completed") + + def planTasks(self): + self._tasks.append(self.launchTask(self.job(None, "task0"))) + self._tasks.append(self.launchTask(self.job(self._tasks[0], "task1"))) + self._tasks.append(self.launchTask(self.job(self._tasks[1], "task2"))) + + async def waitForTask(self, taskIdx): + return await self._tasks[taskIdx] + + def syncWaitForTask(self, taskIdx): + return self._eventLoop.run_until_complete(self._tasks[taskIdx]) + + +async def async_test(): + print("--- async test") + worker = Worker() + worker.planTasks() + await worker.waitForTask(0) + print("-> task0 done") + await worker.waitForTask(2) + print("-> task2 done") + + +def sync_test(): + print("--- sync test") + worker = Worker() + worker.planTasks() + worker.syncWaitForTask(0) + print("-> task0 done") + worker.syncWaitForTask(2) + print("-> task2 done") + + +asyncio.get_event_loop().run_until_complete(async_test()) +sync_test() diff --git a/tests/run-tests.py b/tests/run-tests.py index 9e7cab4689a8d..ac411a0be6ada 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -162,6 +162,9 @@ def open(self, path, mode): "extmod/asyncio_new_event_loop.py", "extmod/asyncio_threadsafeflag.py", "extmod/asyncio_wait_for_fwd.py", + "extmod/asyncio_event_queue.py", + "extmod/asyncio_iterator_event.py", + "extmod/asyncio_wait_for_linked_task.py", "extmod/binascii_a2b_base64.py", "extmod/deflate_compress_memory_error.py", # tries to allocate unlimited memory "extmod/re_stack_overflow.py", @@ -843,6 +846,8 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): ) # native doesn't have proper traceback info skip_tests.add("micropython/schedule.py") # native code doesn't check pending events skip_tests.add("stress/bytecode_limit.py") # bytecode specific test + skip_tests.add("extmod/asyncio_event_queue.py") # native can't run schedule + skip_tests.add("extmod/asyncio_iterator_event.py") # native can't run schedule def run_one_test(test_file): test_file = test_file.replace("\\", "/") From 288dce26bd1f6c4bf664aefe11103840e458ebb2 Mon Sep 17 00:00:00 2001 From: Malcolm McKellips Date: Tue, 6 May 2025 16:25:53 -0600 Subject: [PATCH 062/154] docs,ports: Fix SparkFun capitalization. This is a follow-up to 1e92bdd206f6f87ba65ea05c5b2623fca0b926cd correcting more of the instances where "Sparkfun" should be "SparkFun". Signed-off-by: Damien George --- docs/library/wm8960.rst | 8 ++++---- docs/samd/pinout.rst | 2 +- ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/board.json | 4 ++-- .../boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h | 2 +- ports/samd/modmachine.c | 2 +- ports/stm32/boards/SPARKFUN_MICROMOD_STM32/board.json | 2 +- .../stm32/boards/SPARKFUN_MICROMOD_STM32/mpconfigboard.h | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/library/wm8960.rst b/docs/library/wm8960.rst index 5abfb6a8a011c..4115d20758100 100644 --- a/docs/library/wm8960.rst +++ b/docs/library/wm8960.rst @@ -358,13 +358,13 @@ Run WM8960 on a MIMXRT10xx_DEV board in secondary mode (default):: sysclk_source=wm8960.SYSCLK_MCLK) -Record with a Sparkfun WM8960 breakout board with Teensy in secondary mode (default):: +Record with a SparkFun WM8960 breakout board with Teensy in secondary mode (default):: # Micro_python WM8960 Codec driver # # The breakout board uses a fixed 24MHz MCLK. Therefore the internal # PLL must be used as sysclk, which is the master audio clock. - # The Sparkfun board has the WS pins for RX and TX connected on the + # The SparkFun board has the WS pins for RX and TX connected on the # board. Therefore adc_sync must be set to sync_adc, to configure # it's ADCLRC pin as input. # @@ -379,11 +379,11 @@ Record with a Sparkfun WM8960 breakout board with Teensy in secondary mode (defa right_input=wm8960.INPUT_CLOSED) -Play with a Sparkfun WM8960 breakout board with Teensy in secondary mode (default):: +Play with a SparkFun WM8960 breakout board with Teensy in secondary mode (default):: # The breakout board uses a fixed 24MHz MCLK. Therefore the internal # PLL must be used as sysclk, which is the master audio clock. - # The Sparkfun board has the WS pins for RX and TX connected on the + # The SparkFun board has the WS pins for RX and TX connected on the # board. Therefore adc_sync must be set to sync_adc, to configure # it's ADCLRC pin as input. diff --git a/docs/samd/pinout.rst b/docs/samd/pinout.rst index 3945e2bf2ff1c..1ad8f55884060 100644 --- a/docs/samd/pinout.rst +++ b/docs/samd/pinout.rst @@ -892,7 +892,7 @@ Default pin assignments: There seems to be no default pin assignment for this board. -Sparkfun SAMD51 Thing Plus pin assignment table +SparkFun SAMD51 Thing Plus pin assignment table ------------------------------------------------ === ==== ============ ==== ==== ==== ====== ====== ===== ===== ===== diff --git a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/board.json b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/board.json index ee9ca9d3689b0..51d124e751d6e 100644 --- a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/board.json +++ b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/board.json @@ -13,8 +13,8 @@ "sparkfun_samd51_thing_plus.jpg" ], "mcu": "samd51", - "product": "Sparkfun SAMD51 Thing Plus", + "product": "SparkFun SAMD51 Thing Plus", "thumbnail": "", "url": "https://www.sparkfun.com/products/14713", - "vendor": "Sparkfun" + "vendor": "SparkFun" } diff --git a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h index fe2226a59eb58..9e0db7875f247 100644 --- a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h +++ b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h @@ -1,4 +1,4 @@ -#define MICROPY_HW_BOARD_NAME "Sparkfun SAMD51 Thing Plus" +#define MICROPY_HW_BOARD_NAME "SparkFun SAMD51 Thing Plus" #define MICROPY_HW_MCU_NAME "SAMD51J20A" #define MICROPY_HW_XOSC32K (1) diff --git a/ports/samd/modmachine.c b/ports/samd/modmachine.c index 65dbf8a7f5039..82e7b7c062045 100644 --- a/ports/samd/modmachine.c +++ b/ports/samd/modmachine.c @@ -42,7 +42,7 @@ #define DBL_TAP_ADDR ((volatile uint32_t *)(HSRAM_ADDR + HSRAM_SIZE - 4)) #endif // A board may define a DPL_TAP_ADDR_ALT, which will be set as well -// Needed at the moment for Sparkfun SAMD51 Thing Plus +// Needed at the moment for SparkFun SAMD51 Thing Plus #define DBL_TAP_MAGIC_LOADER 0xf01669ef #define DBL_TAP_MAGIC_RESET 0xf02669ef diff --git a/ports/stm32/boards/SPARKFUN_MICROMOD_STM32/board.json b/ports/stm32/boards/SPARKFUN_MICROMOD_STM32/board.json index 0bd3573d6445e..01ed363cf3740 100644 --- a/ports/stm32/boards/SPARKFUN_MICROMOD_STM32/board.json +++ b/ports/stm32/boards/SPARKFUN_MICROMOD_STM32/board.json @@ -11,5 +11,5 @@ "product": "Micromod STM32", "thumbnail": "", "url": "", - "vendor": "Sparkfun" + "vendor": "SparkFun" } diff --git a/ports/stm32/boards/SPARKFUN_MICROMOD_STM32/mpconfigboard.h b/ports/stm32/boards/SPARKFUN_MICROMOD_STM32/mpconfigboard.h index d2f44cf6cccf0..1e17905bb0d6c 100644 --- a/ports/stm32/boards/SPARKFUN_MICROMOD_STM32/mpconfigboard.h +++ b/ports/stm32/boards/SPARKFUN_MICROMOD_STM32/mpconfigboard.h @@ -1,4 +1,4 @@ -// The Sparkfun MicroMod spec uses a zero-based peripheral numbering scheme. +// The SparkFun MicroMod spec uses a zero-based peripheral numbering scheme. // In cases where the 0th peripheral is the default, the "0" is omitted from // the name (e.g. "I2C" instead of "I2C0"). // From 8e328da9bf96b4347bd4746afc337fe28f2e75b1 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sun, 9 Feb 2025 12:49:45 +0100 Subject: [PATCH 063/154] samd/boards/SAMD_GENERIC_Dxxx: Add Microchip URL to board.json. To the Microchip Web site. Thanks to Matt Trentini for suggesting this site. Signed-off-by: robert-hh --- ports/samd/boards/SAMD_GENERIC_D21X18/board.json | 1 + ports/samd/boards/SAMD_GENERIC_D51X19/board.json | 1 + ports/samd/boards/SAMD_GENERIC_D51X20/board.json | 1 + 3 files changed, 3 insertions(+) diff --git a/ports/samd/boards/SAMD_GENERIC_D21X18/board.json b/ports/samd/boards/SAMD_GENERIC_D21X18/board.json index a14730bd1bf49..c00d78addd40e 100644 --- a/ports/samd/boards/SAMD_GENERIC_D21X18/board.json +++ b/ports/samd/boards/SAMD_GENERIC_D21X18/board.json @@ -11,6 +11,7 @@ ], "mcu": "samd21", "vendor": "Microchip", + "url": "https://www.microchip.com/en-us/products/microcontrollers-and-microprocessors/32-bit-mcus/sam-32-bit-mcus/sam-d", "product": "Generic SAMD21J18", "thumbnail": "" } diff --git a/ports/samd/boards/SAMD_GENERIC_D51X19/board.json b/ports/samd/boards/SAMD_GENERIC_D51X19/board.json index 21cb114bf65fb..2be5f20851eb7 100644 --- a/ports/samd/boards/SAMD_GENERIC_D51X19/board.json +++ b/ports/samd/boards/SAMD_GENERIC_D51X19/board.json @@ -11,6 +11,7 @@ ], "mcu": "samd51", "vendor": "Microchip", + "url": "https://www.microchip.com/en-us/products/microcontrollers-and-microprocessors/32-bit-mcus/sam-32-bit-mcus/sam-d", "product": "Generic SAMD51P19", "thumbnail": "" } diff --git a/ports/samd/boards/SAMD_GENERIC_D51X20/board.json b/ports/samd/boards/SAMD_GENERIC_D51X20/board.json index ae4f83472ecd4..8384484327894 100644 --- a/ports/samd/boards/SAMD_GENERIC_D51X20/board.json +++ b/ports/samd/boards/SAMD_GENERIC_D51X20/board.json @@ -11,6 +11,7 @@ ], "mcu": "samd51", "vendor": "Microchip", + "url": "https://www.microchip.com/en-us/products/microcontrollers-and-microprocessors/32-bit-mcus/sam-32-bit-mcus/sam-d", "product": "Generic SAMD51P20", "thumbnail": "" } From 2fda4bbe05799ccf387051acec600c6ac2e243e2 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 12 Mar 2025 20:28:51 +0100 Subject: [PATCH 064/154] samd/machine_i2c: Add the timeout keyword argument to the constructor. To make it compliant with the documentation. The default value is 50000us. Signed-off-by: robert-hh --- ports/samd/machine_i2c.c | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/ports/samd/machine_i2c.c b/ports/samd/machine_i2c.c index 03af3d29ed614..172518523d208 100644 --- a/ports/samd/machine_i2c.c +++ b/ports/samd/machine_i2c.c @@ -37,9 +37,9 @@ #include "genhdr/pins.h" #include "clock_config.h" -#define DEFAULT_I2C_FREQ (400000) -#define RISETIME_NS (200) -#define I2C_TIMEOUT (100) +#define DEFAULT_I2C_FREQ (400000) +#define RISETIME_NS (200) +#define DEFAULT_I2C_TIMEOUT (50000) #define IS_BUS_BUSY (i2c->I2CM.STATUS.bit.BUSSTATE == 3) #define NACK_RECVD (i2c->I2CM.STATUS.bit.RXNACK == 1) @@ -67,6 +67,7 @@ typedef struct _machine_i2c_obj_t { uint8_t state; uint32_t freq; uint32_t timeout; + uint32_t timer; size_t len; uint8_t *buf; } machine_i2c_obj_t; @@ -88,7 +89,7 @@ void common_i2c_irq_handler(int i2c_id) { if (self->len > 0) { *(self->buf)++ = i2c->I2CM.DATA.reg; self->len--; - self->timeout = I2C_TIMEOUT; + self->timer = self->timeout; } if (self->len > 0) { // no ACK at the last byte PREPARE_ACK; // Send ACK @@ -105,7 +106,7 @@ void common_i2c_irq_handler(int i2c_id) { } else if (self->len > 0) { // data to be sent i2c->I2CM.DATA.bit.DATA = *(self->buf)++; self->len--; - self->timeout = I2C_TIMEOUT; + self->timer = self->timeout; } else { // No data left, if there was any. self->state = state_done; i2c->I2CM.INTFLAG.reg |= SERCOM_I2CM_INTFLAG_MB; @@ -120,12 +121,13 @@ void common_i2c_irq_handler(int i2c_id) { static void machine_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "I2C(%u, freq=%u, scl=\"%q\", sda=\"%q\")", - self->id, self->freq, pin_find_by_id(self->scl)->name, pin_find_by_id(self->sda)->name); + mp_printf(print, "I2C(%u, freq=%u, scl=\"%q\", sda=\"%q\", timeout=%u)", + self->id, self->freq, pin_find_by_id(self->scl)->name, pin_find_by_id(self->sda)->name, + self->timeout * 1000); } mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_id, ARG_freq, ARG_scl, ARG_sda }; + enum { ARG_id, ARG_freq, ARG_scl, ARG_sda, ARG_timeout }; static const mp_arg_t allowed_args[] = { #if MICROPY_HW_DEFAULT_I2C_ID < 0 { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, @@ -140,6 +142,7 @@ mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n { MP_QSTR_scl, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_sda, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, #endif + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_I2C_TIMEOUT} }, }; // Parse args. @@ -168,6 +171,8 @@ mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n } MP_STATE_PORT(sercom_table[self->id]) = self; self->freq = args[ARG_freq].u_int; + // The unit for ARG_timeout is us, but the code uses ms. + self->timeout = args[ARG_timeout].u_int / 1000; // Configure the Pin mux. mp_hal_set_pin_mux(self->scl, scl_pad_config.alt_fct); @@ -224,13 +229,13 @@ static int machine_i2c_transfer_single(mp_obj_base_t *self_in, uint16_t addr, si machine_i2c_obj_t *self = (machine_i2c_obj_t *)self_in; Sercom *i2c = self->instance; - self->timeout = I2C_TIMEOUT; + self->timer = self->timeout; self->len = len; self->buf = buf; // Wait a while if the bus is busy - while (IS_BUS_BUSY && self->timeout) { + while (IS_BUS_BUSY && self->timer) { MICROPY_EVENT_POLL_HOOK - if (--self->timeout == 0) { + if (--self->timer == 0) { return -MP_ETIMEDOUT; } } @@ -242,9 +247,9 @@ static int machine_i2c_transfer_single(mp_obj_base_t *self_in, uint16_t addr, si i2c->I2CM.ADDR.bit.ADDR = (addr << 1) | READ_MODE; // Transfer the data - self->timeout = I2C_TIMEOUT; - while (self->state == state_busy && self->timeout) { - self->timeout--; + self->timer = self->timeout; + while (self->state == state_busy && self->timer) { + self->timer--; MICROPY_EVENT_POLL_HOOK } i2c->I2CM.INTENCLR.reg = SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB | SERCOM_I2CM_INTENSET_ERROR; @@ -256,7 +261,7 @@ static int machine_i2c_transfer_single(mp_obj_base_t *self_in, uint16_t addr, si } else if (self->state == state_buserr) { SET_STOP_STATE; return -MP_EIO; - } else if (self->timeout == 0) { + } else if (self->timer == 0) { SET_STOP_STATE; return -MP_ETIMEDOUT; } From ff9e01782b7e0f21d8d085bdc4c917a7faf6c17e Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 26 Mar 2025 09:24:19 +0100 Subject: [PATCH 065/154] samd/modtime: Change time.time_ns() to follow the RTC time. That is done by adding the offset to epoch, following the scheme from the RP2 port. RTC and `ticks_us()` are not precisely in sync, and so the difference between `time.time_ns()/1e9` and `time.time()` will increase by more than 9 seconds/24h. So applications should avoid using `time.time()` and `time.time_ns()` in the same context. Signed-off-by: robert-hh --- ports/samd/machine_rtc.c | 1 + ports/samd/main.c | 2 ++ ports/samd/modtime.c | 14 ++++++++++++++ ports/samd/mphalport.h | 5 +---- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/ports/samd/machine_rtc.c b/ports/samd/machine_rtc.c index 74c2266d60926..4b03488b71b73 100644 --- a/ports/samd/machine_rtc.c +++ b/ports/samd/machine_rtc.c @@ -133,6 +133,7 @@ static mp_obj_t machine_rtc_datetime_helper(size_t n_args, const mp_obj_t *args, } #endif + mp_hal_time_ns_set_from_rtc(); return mp_const_none; } } diff --git a/ports/samd/main.c b/ports/samd/main.c index 2bbaf63e6e4c5..a7da95582f76c 100644 --- a/ports/samd/main.c +++ b/ports/samd/main.c @@ -28,6 +28,7 @@ #include "py/runtime.h" #include "py/gc.h" #include "py/mperrno.h" +#include "py/mphal.h" #include "py/stackctrl.h" #include "shared/readline/readline.h" #include "shared/runtime/gchelper.h" @@ -45,6 +46,7 @@ extern void sercom_deinit_all(void); void samd_main(void) { mp_stack_set_top(&_estack); mp_stack_set_limit(&_estack - &_sstack - 1024); + mp_hal_time_ns_set_from_rtc(); for (;;) { gc_init(&_sheap, &_eheap); diff --git a/ports/samd/modtime.c b/ports/samd/modtime.c index 83072dd16feeb..0bed3cb83a868 100644 --- a/ports/samd/modtime.c +++ b/ports/samd/modtime.c @@ -28,6 +28,8 @@ #include "shared/timeutils/timeutils.h" #include "modmachine.h" +static uint64_t time_us_64_offset_from_epoch; + // Return the localtime as an 8-tuple. static mp_obj_t mp_time_localtime_get(void) { timeutils_struct_time_t tm; @@ -54,3 +56,15 @@ static mp_obj_t mp_time_time_get(void) { return mp_obj_new_int_from_uint(timeutils_mktime( tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec)); } + +void mp_hal_time_ns_set_from_rtc(void) { + timeutils_struct_time_t tm; + rtc_gettime(&tm); + uint64_t time_us = (uint64_t)timeutils_mktime(tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, + tm.tm_min, tm.tm_sec) * 1000000ULL; + time_us_64_offset_from_epoch = time_us - mp_hal_ticks_us_64(); +} + +uint64_t mp_hal_time_ns(void) { + return (time_us_64_offset_from_epoch + mp_hal_ticks_us_64()) * 1000ULL; +} diff --git a/ports/samd/mphalport.h b/ports/samd/mphalport.h index 6c9e525bb9162..69e2b606408be 100644 --- a/ports/samd/mphalport.h +++ b/ports/samd/mphalport.h @@ -47,6 +47,7 @@ extern int mp_interrupt_char; extern ringbuf_t stdin_ringbuf; extern volatile uint32_t systick_ms; uint64_t mp_hal_ticks_us_64(void); +void mp_hal_time_ns_set_from_rtc(void); void mp_hal_set_interrupt_char(int c); @@ -94,10 +95,6 @@ static inline mp_uint_t mp_hal_ticks_cpu(void) { } #endif -static inline uint64_t mp_hal_time_ns(void) { - return mp_hal_ticks_us_64() * 1000; -} - // C-level pin HAL #include "py/obj.h" From c22c2c806694dbce55cfe484b1b9e55af33bb950 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 15 Apr 2025 16:57:53 +0200 Subject: [PATCH 066/154] samd/boards/SAMD_GENERIC_D51xxx: Fix VFS settings for internal flash. Fixes in this commit: - The wrong loader script was assigned for SAMD_GENERIC_D51X20, causing the VFS block count to be wrong. - Change the VFS block size from 1536 to 2048. With the setting of 1536, writing more that 1536 bytes at once failed. This applies to SAMD_GENERIC_D51X19 and SAMD_GENERIC_D51X20. No other SAMD51 board uses the internal flash for the file system. Signed-off-by: robert-hh --- ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.mk | 6 ++---- ports/samd/mcu/samd51/mpconfigmcu.h | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.mk b/ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.mk index ddba3dcbbade9..b240c2587f8d0 100644 --- a/ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.mk +++ b/ports/samd/boards/SAMD_GENERIC_D51X20/mpconfigboard.mk @@ -1,13 +1,11 @@ MCU_SERIES = SAMD51 CMSIS_MCU = SAMD51P20A -LD_FILES = boards/samd51x19a.ld sections.ld +LD_FILES = boards/samd51x20a.ld sections.ld TEXT0 = 0x4000 - # The ?='s allow overriding in mpconfigboard.mk. # MicroPython settings # The size of a MCU flash filesystem will be -# 1008k - MICROPY_HW_CODESIZE - MICROPY_HW_VFSROMSIZE +# 1008k - MICROPY_HW_CODESIZE # The default for MICROPY_HW_VFSROMSIZE is 64K MICROPY_HW_CODESIZE ?= 752K -MICROPY_HW_VFSROMSIZE ?= 128K diff --git a/ports/samd/mcu/samd51/mpconfigmcu.h b/ports/samd/mcu/samd51/mpconfigmcu.h index 831949bab56a0..8cce90b886c07 100644 --- a/ports/samd/mcu/samd51/mpconfigmcu.h +++ b/ports/samd/mcu/samd51/mpconfigmcu.h @@ -23,7 +23,7 @@ unsigned long trng_random_u32(void); #define MICROPY_FATFS_MAX_SS (4096) #define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ -#define VFS_BLOCK_SIZE_BYTES (1536) // +#define VFS_BLOCK_SIZE_BYTES (2048) // #ifndef MICROPY_HW_UART_TXBUF #define MICROPY_HW_UART_TXBUF (1) From 2e6df08e05f4832806f062c56f78c5412fd37ceb Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 9 Apr 2025 18:07:24 +0200 Subject: [PATCH 067/154] samd/samd_spiflash: Improve the flash type detection. Changes in this commit: - Check for the proper SFDP header. - Use the flash size information from SFDP, if present. - Add two more special flash chips <= 1 MByte without SFDP. JEDEC-ID table for special flash types instead of a series of conditional statements. - Add a compile flag `MICROPY_HW_SPIFLASH_SIZE` to set the size in `mpconfigboard.h`, which replaces getting the size from the JEDEC ID or the SFDP record. Signed-off-by: robert-hh --- ports/samd/samd_spiflash.c | 65 +++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/ports/samd/samd_spiflash.c b/ports/samd/samd_spiflash.c index 8ada7e96092c2..63dc0a83045b1 100644 --- a/ports/samd/samd_spiflash.c +++ b/ports/samd/samd_spiflash.c @@ -67,6 +67,19 @@ typedef struct _spiflash_obj_t { extern const mp_obj_type_t samd_spiflash_type; +typedef struct _spiflash_jedec_id_t { + uint32_t jedec_id; + uint32_t mask; + uint32_t size; +} spiflash_jedec_id_t; + +spiflash_jedec_id_t jedec_id_table[] = { + { 0x1f8401, 0xffff00, 512 * 1024 }, // Adesto/Renesas 4 MBit + { 0x1f2400, 0xffff00, 512 * 1024 }, // Adesto 4 MBit + { 0x1f4501, 0xffff00, 1024 * 1024 }, // Adesto/Renesas/Atmel 8 MBit + { 0xc84013, 0xffffff, 512 * 1024 }, // Gigadevices 4 MBit +}; + // The SPIflash object is a singleton static spiflash_obj_t spiflash_obj = { { &samd_spiflash_type }, NULL, 0, false, PAGE_SIZE, SECTOR_SIZE, NULL, 0 @@ -124,6 +137,7 @@ static void write_enable(spiflash_obj_t *self) { mp_hal_pin_write(self->cs, 1); } +#if !defined(MICROPY_HW_SPIFLASH_SIZE) // Write status register 1 static void write_sr1(spiflash_obj_t *self, uint8_t value) { uint8_t msg[2]; @@ -134,6 +148,7 @@ static void write_sr1(spiflash_obj_t *self, uint8_t value) { spi_transfer(self->spi, 2, msg, NULL); mp_hal_pin_write(self->cs, 1); } +#endif static void get_sfdp(spiflash_obj_t *self, uint32_t addr, uint8_t *buffer, int size) { uint8_t dummy[1]; @@ -170,32 +185,46 @@ static mp_obj_t spiflash_make_new(const mp_obj_type_t *type, size_t n_args, size // Get the flash size from the device ID (default) uint8_t id[3]; get_id(self, id); - bool read_sfdp = true; - - if (id[1] == 0x84 && id[2] == 1) { // Adesto - self->size = 512 * 1024; - } else if (id[0] == 0x1f && id[1] == 0x45 && id[2] == 1) { // Adesto/Renesas 8 MBit - self->size = 1024 * 1024; - read_sfdp = false; - self->sectorsize = 4096; - self->addr_is_32bit = false; - // Globally unlock the sectors, which are locked after power on. - write_enable(self); - write_sr1(self, 0); - } else { - self->size = 1 << id[2]; + + #if defined(MICROPY_HW_SPIFLASH_SIZE) + self->size = MICROPY_HW_SPIFLASH_SIZE; + #else + // Assume as default that the size is coded into the last JEDEC ID byte. + self->size = 1 << id[2]; + // Look for specific flash devices with different encoding. + uint32_t jedec_id = (id[0] << 16) | (id[1] << 8) | id[2]; + for (int i = 0; i < MP_ARRAY_SIZE(jedec_id_table); i++) { + if (jedec_id_table[i].jedec_id == (jedec_id & jedec_id_table[i].mask)) { + self->size = jedec_id_table[i].size; + // Globally unlock the sectors, which may be locked after power on. + write_enable(self); + write_sr1(self, 0); + break; + } } + #endif - // Get the addr_is_32bit flag and the sector size - if (read_sfdp) { - uint8_t buffer[128]; - get_sfdp(self, 0, buffer, 16); // get the header + // Get the flash size, addr_is_32bit flag and sector size from SFDP, if present. + uint8_t buffer[128]; + get_sfdp(self, 0, buffer, 16); // get the header + if (*(uint32_t *)buffer == 0x50444653) { // Header signature "SFDP" int len = MIN(buffer[11] * 4, sizeof(buffer)); if (len >= 29) { int addr = buffer[12] + (buffer[13] << 8) + (buffer[14] << 16); get_sfdp(self, addr, buffer, len); // Get the JEDEC mandatory table self->sectorsize = 1 << buffer[28]; self->addr_is_32bit = ((buffer[2] >> 1) & 0x03) != 0; + #if !defined(MICROPY_HW_SPIFLASH_SIZE) + // Get the bit size from the SFDP data + uint32_t size = *(uint32_t *)(buffer + 4); + if (size & 0x8000000) { + // Byte size is 2 ** lower_31_bits / 8 + self->size = 1 << ((size & 0x7fffffff) >> 3); + } else { + // Byte size is lower_31_bits / 8 + 1 + self->size = ((size & 0x7fffffff) >> 3) + 1; + } + #endif } } self->commands = self->addr_is_32bit ? _COMMANDS_32BIT : _COMMANDS_24BIT; From 80d03b77804da44d56b7b9446520ffafde7e3eea Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 16 Apr 2025 21:46:15 +0200 Subject: [PATCH 068/154] samd/samd_qspiflash: Remove the attempt to handle a unknown device. Since all QSPI flash device used by this port are defined, this code was only used unintentionally. Besides that it was incomplete, so better drop it. Note: The flash type for Mini-SAM had to be changed too. Signed-off-by: robert-hh --- ports/samd/boards/MINISAM_M4/mpconfigboard.h | 2 +- ports/samd/samd_qspiflash.c | 32 +------------------- 2 files changed, 2 insertions(+), 32 deletions(-) diff --git a/ports/samd/boards/MINISAM_M4/mpconfigboard.h b/ports/samd/boards/MINISAM_M4/mpconfigboard.h index 6d908bdcb81d2..30a7a804542cb 100644 --- a/ports/samd/boards/MINISAM_M4/mpconfigboard.h +++ b/ports/samd/boards/MINISAM_M4/mpconfigboard.h @@ -7,4 +7,4 @@ #define MICROPY_HW_DEFAULT_I2C_ID (2) #define MICROPY_HW_DEFAULT_SPI_ID (1) -#define MICROPY_HW_QSPIFLASH GD25Q16C +#define MICROPY_HW_QSPIFLASH W25Q16JV_IQ diff --git a/ports/samd/samd_qspiflash.c b/ports/samd/samd_qspiflash.c index e25ddf21efe8f..f314a955197d6 100644 --- a/ports/samd/samd_qspiflash.c +++ b/ports/samd/samd_qspiflash.c @@ -105,7 +105,6 @@ static const external_flash_device possible_devices[] = { #define EXTERNAL_FLASH_DEVICE_COUNT MP_ARRAY_SIZE(possible_devices) static external_flash_device const *flash_device; -static external_flash_device generic_config = GENERIC; extern const mp_obj_type_t samd_qspiflash_type; // The QSPIflash object is a singleton @@ -248,15 +247,6 @@ static uint8_t get_baud(int32_t freq_mhz) { return baud; } -int get_sfdp_table(uint8_t *table, int maxlen) { - uint8_t header[16]; - read_memory_single(QSPI_CMD_READ_SFDP_PARAMETER, 0, header, sizeof(header)); - int len = MIN(header[11] * 4, maxlen); - int addr = header[12] + (header[13] << 8) + (header[14] << 16); - read_memory_single(QSPI_CMD_READ_SFDP_PARAMETER, addr, table, len); - return len; -} - static mp_obj_t samd_qspiflash_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { mp_arg_check_num(n_args, n_kw, 0, 0, false); @@ -297,19 +287,6 @@ static mp_obj_t samd_qspiflash_make_new(const mp_obj_type_t *type, size_t n_args uint8_t jedec_ids[3]; read_command(QSPI_CMD_READ_JEDEC_ID, jedec_ids, sizeof(jedec_ids)); - // Read the common sfdp table - // Check the device addr length, support of 1-1-4 mode and get the sector size - uint8_t sfdp_table[128]; - int len = get_sfdp_table(sfdp_table, sizeof(sfdp_table)); - if (len >= 29) { - self->sectorsize = 1 << sfdp_table[28]; - bool addr4b = ((sfdp_table[2] >> 1) & 0x03) == 0x02; - bool supports_qspi_114 = (sfdp_table[2] & 0x40) != 0; - if (addr4b || !supports_qspi_114) { - mp_raise_ValueError(MP_ERROR_TEXT("QSPI mode not supported")); - } - } - // Check, if the flash device is known and get it's properties. flash_device = NULL; for (uint8_t i = 0; i < EXTERNAL_FLASH_DEVICE_COUNT; i++) { @@ -321,15 +298,8 @@ static mp_obj_t samd_qspiflash_make_new(const mp_obj_type_t *type, size_t n_args break; } } - - // If the flash device is not known, try generic config options if (flash_device == NULL) { - if (jedec_ids[0] == 0xc2) { // Macronix devices - generic_config.quad_enable_bit_mask = 0x04; - generic_config.single_status_byte = true; - } - generic_config.total_size = 1 << jedec_ids[2]; - flash_device = &generic_config; + mp_raise_ValueError(MP_ERROR_TEXT("QSPI device not supported")); } self->size = flash_device->total_size; From 9287a1e6eaa9c6dc1bb9be27466acd3e639d8113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Wed, 27 Mar 2024 20:16:13 +0100 Subject: [PATCH 069/154] lib/littlefs: Update LittleFS to v2.10.2. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Going above the root directory (/../foo) now gives an error. This is an intentional change made by LittleFS. It required a update of the testsuite and is a (minor) compatibility break. Signed-off-by: DaniĆ«l van de Giessen --- lib/littlefs/lfs2.c | 785 ++++++++++++++++++++++++--------------- lib/littlefs/lfs2.h | 94 +++-- lib/littlefs/lfs2_util.c | 3 + lib/littlefs/lfs2_util.h | 40 +- tests/extmod/vfs_lfs.py | 2 +- 5 files changed, 585 insertions(+), 339 deletions(-) diff --git a/lib/littlefs/lfs2.c b/lib/littlefs/lfs2.c index d89c42fd59a21..f9ce2cf290686 100644 --- a/lib/littlefs/lfs2.c +++ b/lib/littlefs/lfs2.c @@ -282,6 +282,21 @@ static int lfs2_bd_erase(lfs2_t *lfs2, lfs2_block_t block) { /// Small type-level utilities /// + +// some operations on paths +static inline lfs2_size_t lfs2_path_namelen(const char *path) { + return strcspn(path, "/"); +} + +static inline bool lfs2_path_islast(const char *path) { + lfs2_size_t namelen = lfs2_path_namelen(path); + return path[namelen + strspn(path + namelen, "/")] == '\0'; +} + +static inline bool lfs2_path_isdir(const char *path) { + return path[lfs2_path_namelen(path)] != '\0'; +} + // operations on block pairs static inline void lfs2_pair_swap(lfs2_block_t pair[2]) { lfs2_block_t t = pair[0]; @@ -389,18 +404,15 @@ struct lfs2_diskoff { // operations on global state static inline void lfs2_gstate_xor(lfs2_gstate_t *a, const lfs2_gstate_t *b) { - for (int i = 0; i < 3; i++) { - ((uint32_t*)a)[i] ^= ((const uint32_t*)b)[i]; - } + a->tag ^= b->tag; + a->pair[0] ^= b->pair[0]; + a->pair[1] ^= b->pair[1]; } static inline bool lfs2_gstate_iszero(const lfs2_gstate_t *a) { - for (int i = 0; i < 3; i++) { - if (((uint32_t*)a)[i] != 0) { - return false; - } - } - return true; + return a->tag == 0 + && a->pair[0] == 0 + && a->pair[1] == 0; } #ifndef LFS2_READONLY @@ -550,9 +562,9 @@ static int lfs2_dir_compact(lfs2_t *lfs2, lfs2_mdir_t *source, uint16_t begin, uint16_t end); static lfs2_ssize_t lfs2_file_flushedwrite(lfs2_t *lfs2, lfs2_file_t *file, const void *buffer, lfs2_size_t size); -static lfs2_ssize_t lfs2_file_rawwrite(lfs2_t *lfs2, lfs2_file_t *file, +static lfs2_ssize_t lfs2_file_write_(lfs2_t *lfs2, lfs2_file_t *file, const void *buffer, lfs2_size_t size); -static int lfs2_file_rawsync(lfs2_t *lfs2, lfs2_file_t *file); +static int lfs2_file_sync_(lfs2_t *lfs2, lfs2_file_t *file); static int lfs2_file_outline(lfs2_t *lfs2, lfs2_file_t *file); static int lfs2_file_flush(lfs2_t *lfs2, lfs2_file_t *file); @@ -574,65 +586,72 @@ static int lfs21_traverse(lfs2_t *lfs2, int (*cb)(void*, lfs2_block_t), void *data); #endif -static int lfs2_dir_rawrewind(lfs2_t *lfs2, lfs2_dir_t *dir); +static int lfs2_dir_rewind_(lfs2_t *lfs2, lfs2_dir_t *dir); static lfs2_ssize_t lfs2_file_flushedread(lfs2_t *lfs2, lfs2_file_t *file, void *buffer, lfs2_size_t size); -static lfs2_ssize_t lfs2_file_rawread(lfs2_t *lfs2, lfs2_file_t *file, +static lfs2_ssize_t lfs2_file_read_(lfs2_t *lfs2, lfs2_file_t *file, void *buffer, lfs2_size_t size); -static int lfs2_file_rawclose(lfs2_t *lfs2, lfs2_file_t *file); -static lfs2_soff_t lfs2_file_rawsize(lfs2_t *lfs2, lfs2_file_t *file); +static int lfs2_file_close_(lfs2_t *lfs2, lfs2_file_t *file); +static lfs2_soff_t lfs2_file_size_(lfs2_t *lfs2, lfs2_file_t *file); -static lfs2_ssize_t lfs2_fs_rawsize(lfs2_t *lfs2); -static int lfs2_fs_rawtraverse(lfs2_t *lfs2, +static lfs2_ssize_t lfs2_fs_size_(lfs2_t *lfs2); +static int lfs2_fs_traverse_(lfs2_t *lfs2, int (*cb)(void *data, lfs2_block_t block), void *data, bool includeorphans); static int lfs2_deinit(lfs2_t *lfs2); -static int lfs2_rawunmount(lfs2_t *lfs2); +static int lfs2_unmount_(lfs2_t *lfs2); /// Block allocator /// + +// allocations should call this when all allocated blocks are committed to +// the filesystem +// +// after a checkpoint, the block allocator may realloc any untracked blocks +static void lfs2_alloc_ckpoint(lfs2_t *lfs2) { + lfs2->lookahead.ckpoint = lfs2->block_count; +} + +// drop the lookahead buffer, this is done during mounting and failed +// traversals in order to avoid invalid lookahead state +static void lfs2_alloc_drop(lfs2_t *lfs2) { + lfs2->lookahead.size = 0; + lfs2->lookahead.next = 0; + lfs2_alloc_ckpoint(lfs2); +} + #ifndef LFS2_READONLY static int lfs2_alloc_lookahead(void *p, lfs2_block_t block) { lfs2_t *lfs2 = (lfs2_t*)p; - lfs2_block_t off = ((block - lfs2->free.off) + lfs2_block_t off = ((block - lfs2->lookahead.start) + lfs2->block_count) % lfs2->block_count; - if (off < lfs2->free.size) { - lfs2->free.buffer[off / 32] |= 1U << (off % 32); + if (off < lfs2->lookahead.size) { + lfs2->lookahead.buffer[off / 8] |= 1U << (off % 8); } return 0; } #endif -// indicate allocated blocks have been committed into the filesystem, this -// is to prevent blocks from being garbage collected in the middle of a -// commit operation -static void lfs2_alloc_ack(lfs2_t *lfs2) { - lfs2->free.ack = lfs2->block_count; -} - -// drop the lookahead buffer, this is done during mounting and failed -// traversals in order to avoid invalid lookahead state -static void lfs2_alloc_drop(lfs2_t *lfs2) { - lfs2->free.size = 0; - lfs2->free.i = 0; - lfs2_alloc_ack(lfs2); -} - #ifndef LFS2_READONLY -static int lfs2_fs_rawgc(lfs2_t *lfs2) { - // Move free offset at the first unused block (lfs2->free.i) - // lfs2->free.i is equal lfs2->free.size when all blocks are used - lfs2->free.off = (lfs2->free.off + lfs2->free.i) % lfs2->block_count; - lfs2->free.size = lfs2_min(8*lfs2->cfg->lookahead_size, lfs2->free.ack); - lfs2->free.i = 0; +static int lfs2_alloc_scan(lfs2_t *lfs2) { + // move lookahead buffer to the first unused block + // + // note we limit the lookahead buffer to at most the amount of blocks + // checkpointed, this prevents the math in lfs2_alloc from underflowing + lfs2->lookahead.start = (lfs2->lookahead.start + lfs2->lookahead.next) + % lfs2->block_count; + lfs2->lookahead.next = 0; + lfs2->lookahead.size = lfs2_min( + 8*lfs2->cfg->lookahead_size, + lfs2->lookahead.ckpoint); // find mask of free blocks from tree - memset(lfs2->free.buffer, 0, lfs2->cfg->lookahead_size); - int err = lfs2_fs_rawtraverse(lfs2, lfs2_alloc_lookahead, lfs2, true); + memset(lfs2->lookahead.buffer, 0, lfs2->cfg->lookahead_size); + int err = lfs2_fs_traverse_(lfs2, lfs2_alloc_lookahead, lfs2, true); if (err) { lfs2_alloc_drop(lfs2); return err; @@ -645,36 +664,49 @@ static int lfs2_fs_rawgc(lfs2_t *lfs2) { #ifndef LFS2_READONLY static int lfs2_alloc(lfs2_t *lfs2, lfs2_block_t *block) { while (true) { - while (lfs2->free.i != lfs2->free.size) { - lfs2_block_t off = lfs2->free.i; - lfs2->free.i += 1; - lfs2->free.ack -= 1; - - if (!(lfs2->free.buffer[off / 32] & (1U << (off % 32)))) { + // scan our lookahead buffer for free blocks + while (lfs2->lookahead.next < lfs2->lookahead.size) { + if (!(lfs2->lookahead.buffer[lfs2->lookahead.next / 8] + & (1U << (lfs2->lookahead.next % 8)))) { // found a free block - *block = (lfs2->free.off + off) % lfs2->block_count; - - // eagerly find next off so an alloc ack can - // discredit old lookahead blocks - while (lfs2->free.i != lfs2->free.size && - (lfs2->free.buffer[lfs2->free.i / 32] - & (1U << (lfs2->free.i % 32)))) { - lfs2->free.i += 1; - lfs2->free.ack -= 1; + *block = (lfs2->lookahead.start + lfs2->lookahead.next) + % lfs2->block_count; + + // eagerly find next free block to maximize how many blocks + // lfs2_alloc_ckpoint makes available for scanning + while (true) { + lfs2->lookahead.next += 1; + lfs2->lookahead.ckpoint -= 1; + + if (lfs2->lookahead.next >= lfs2->lookahead.size + || !(lfs2->lookahead.buffer[lfs2->lookahead.next / 8] + & (1U << (lfs2->lookahead.next % 8)))) { + return 0; + } } - - return 0; } + + lfs2->lookahead.next += 1; + lfs2->lookahead.ckpoint -= 1; } - // check if we have looked at all blocks since last ack - if (lfs2->free.ack == 0) { - LFS2_ERROR("No more free space %"PRIu32, - lfs2->free.i + lfs2->free.off); + // In order to keep our block allocator from spinning forever when our + // filesystem is full, we mark points where there are no in-flight + // allocations with a checkpoint before starting a set of allocations. + // + // If we've looked at all blocks since the last checkpoint, we report + // the filesystem as out of storage. + // + if (lfs2->lookahead.ckpoint <= 0) { + LFS2_ERROR("No more free space 0x%"PRIx32, + (lfs2->lookahead.start + lfs2->lookahead.next) + % lfs2->block_count); return LFS2_ERR_NOSPC; } - int err = lfs2_fs_rawgc(lfs2); + // No blocks in our lookahead buffer, we need to scan the filesystem for + // unused blocks in the next lookahead window. + int err = lfs2_alloc_scan(lfs2); if(err) { return err; } @@ -690,11 +722,14 @@ static lfs2_stag_t lfs2_dir_getslice(lfs2_t *lfs2, const lfs2_mdir_t *dir, lfs2_tag_t ntag = dir->etag; lfs2_stag_t gdiff = 0; + // synthetic moves if (lfs2_gstate_hasmovehere(&lfs2->gdisk, dir->pair) && - lfs2_tag_id(gmask) != 0 && - lfs2_tag_id(lfs2->gdisk.tag) <= lfs2_tag_id(gtag)) { - // synthetic moves - gdiff -= LFS2_MKTAG(0, 1, 0); + lfs2_tag_id(gmask) != 0) { + if (lfs2_tag_id(lfs2->gdisk.tag) == lfs2_tag_id(gtag)) { + return LFS2_ERR_NOENT; + } else if (lfs2_tag_id(lfs2->gdisk.tag) < lfs2_tag_id(gtag)) { + gdiff -= LFS2_MKTAG(0, 1, 0); + } } // iterate over dir block backwards (for faster lookups) @@ -1438,32 +1473,46 @@ static int lfs2_dir_find_match(void *data, return LFS2_CMP_EQ; } +// lfs2_dir_find tries to set path and id even if file is not found +// +// returns: +// - 0 if file is found +// - LFS2_ERR_NOENT if file or parent is not found +// - LFS2_ERR_NOTDIR if parent is not a dir static lfs2_stag_t lfs2_dir_find(lfs2_t *lfs2, lfs2_mdir_t *dir, const char **path, uint16_t *id) { // we reduce path to a single name if we can find it const char *name = *path; - if (id) { - *id = 0x3ff; - } // default to root dir lfs2_stag_t tag = LFS2_MKTAG(LFS2_TYPE_DIR, 0x3ff, 0); dir->tail[0] = lfs2->root[0]; dir->tail[1] = lfs2->root[1]; + // empty paths are not allowed + if (*name == '\0') { + return LFS2_ERR_INVAL; + } + while (true) { nextname: - // skip slashes - name += strspn(name, "/"); + // skip slashes if we're a directory + if (lfs2_tag_type3(tag) == LFS2_TYPE_DIR) { + name += strspn(name, "/"); + } lfs2_size_t namelen = strcspn(name, "/"); - // skip '.' and root '..' - if ((namelen == 1 && memcmp(name, ".", 1) == 0) || - (namelen == 2 && memcmp(name, "..", 2) == 0)) { + // skip '.' + if (namelen == 1 && memcmp(name, ".", 1) == 0) { name += namelen; goto nextname; } + // error on unmatched '..', trying to go above root? + if (namelen == 2 && memcmp(name, "..", 2) == 0) { + return LFS2_ERR_INVAL; + } + // skip if matched by '..' in name const char *suffix = name + namelen; lfs2_size_t sufflen; @@ -1475,7 +1524,9 @@ static lfs2_stag_t lfs2_dir_find(lfs2_t *lfs2, lfs2_mdir_t *dir, break; } - if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) { + if (sufflen == 1 && memcmp(suffix, ".", 1) == 0) { + // noop + } else if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) { depth -= 1; if (depth == 0) { name = suffix + sufflen; @@ -1489,14 +1540,14 @@ static lfs2_stag_t lfs2_dir_find(lfs2_t *lfs2, lfs2_mdir_t *dir, } // found path - if (name[0] == '\0') { + if (*name == '\0') { return tag; } // update what we've found so far *path = name; - // only continue if we hit a directory + // only continue if we're a directory if (lfs2_tag_type3(tag) != LFS2_TYPE_DIR) { return LFS2_ERR_NOTDIR; } @@ -1516,8 +1567,7 @@ static lfs2_stag_t lfs2_dir_find(lfs2_t *lfs2, lfs2_mdir_t *dir, tag = lfs2_dir_fetchmatch(lfs2, dir, dir->tail, LFS2_MKTAG(0x780, 0, 0), LFS2_MKTAG(LFS2_TYPE_NAME, 0, namelen), - // are we last name? - (strchr(name, '/') == NULL) ? id : NULL, + id, lfs2_dir_find_match, &(struct lfs2_dir_find_match){ lfs2, name, namelen}); if (tag < 0) { @@ -2105,13 +2155,14 @@ static int lfs2_dir_splittingcompact(lfs2_t *lfs2, lfs2_mdir_t *dir, // And we cap at half a block to avoid degenerate cases with // nearly-full metadata blocks. // + lfs2_size_t metadata_max = (lfs2->cfg->metadata_max) + ? lfs2->cfg->metadata_max + : lfs2->cfg->block_size; if (end - split < 0xff && size <= lfs2_min( - lfs2->cfg->block_size - 40, + metadata_max - 40, lfs2_alignup( - (lfs2->cfg->metadata_max - ? lfs2->cfg->metadata_max - : lfs2->cfg->block_size)/2, + metadata_max/2, lfs2->cfg->prog_size))) { break; } @@ -2146,14 +2197,16 @@ static int lfs2_dir_splittingcompact(lfs2_t *lfs2, lfs2_mdir_t *dir, && lfs2_pair_cmp(dir->pair, (const lfs2_block_t[2]){0, 1}) == 0) { // oh no! we're writing too much to the superblock, // should we expand? - lfs2_ssize_t size = lfs2_fs_rawsize(lfs2); + lfs2_ssize_t size = lfs2_fs_size_(lfs2); if (size < 0) { return size; } - // do we have extra space? littlefs can't reclaim this space - // by itself, so expand cautiously - if ((lfs2_size_t)size < lfs2->block_count/2) { + // littlefs cannot reclaim expanded superblocks, so expand cautiously + // + // if our filesystem is more than ~88% full, don't expand, this is + // somewhat arbitrary + if (lfs2->block_count - size > lfs2->block_count/8) { LFS2_DEBUG("Expanding superblock at rev %"PRIu32, dir->rev); int err = lfs2_dir_split(lfs2, dir, attrs, attrcount, source, begin, end); @@ -2166,7 +2219,8 @@ static int lfs2_dir_splittingcompact(lfs2_t *lfs2, lfs2_mdir_t *dir, // we can do, we'll error later if we've become frozen LFS2_WARN("Unable to expand superblock"); } else { - end = begin; + // duplicate the superblock entry into the new superblock + end = 1; } } } @@ -2312,7 +2366,8 @@ fixmlist:; if (d->m.pair != pair) { for (int i = 0; i < attrcount; i++) { if (lfs2_tag_type3(attrs[i].tag) == LFS2_TYPE_DELETE && - d->id == lfs2_tag_id(attrs[i].tag)) { + d->id == lfs2_tag_id(attrs[i].tag) && + d->type != LFS2_TYPE_DIR) { d->m.pair[0] = LFS2_BLOCK_NULL; d->m.pair[1] = LFS2_BLOCK_NULL; } else if (lfs2_tag_type3(attrs[i].tag) == LFS2_TYPE_DELETE && @@ -2333,7 +2388,9 @@ fixmlist:; while (d->id >= d->m.count && d->m.split) { // we split and id is on tail now - d->id -= d->m.count; + if (lfs2_pair_cmp(d->m.tail, lfs2->root) != 0) { + d->id -= d->m.count; + } int err = lfs2_dir_fetch(lfs2, &d->m, d->m.tail); if (err) { return err; @@ -2499,7 +2556,7 @@ static int lfs2_dir_orphaningcommit(lfs2_t *lfs2, lfs2_mdir_t *dir, if (err != LFS2_ERR_NOENT) { if (lfs2_gstate_hasorphans(&lfs2->gstate)) { // next step, clean up orphans - err = lfs2_fs_preporphans(lfs2, -hasparent); + err = lfs2_fs_preporphans(lfs2, -(int8_t)hasparent); if (err) { return err; } @@ -2564,7 +2621,7 @@ static int lfs2_dir_commit(lfs2_t *lfs2, lfs2_mdir_t *dir, /// Top level directory operations /// #ifndef LFS2_READONLY -static int lfs2_rawmkdir(lfs2_t *lfs2, const char *path) { +static int lfs2_mkdir_(lfs2_t *lfs2, const char *path) { // deorphan if we haven't yet, needed at most once after poweron int err = lfs2_fs_forceconsistency(lfs2); if (err) { @@ -2575,18 +2632,18 @@ static int lfs2_rawmkdir(lfs2_t *lfs2, const char *path) { cwd.next = lfs2->mlist; uint16_t id; err = lfs2_dir_find(lfs2, &cwd.m, &path, &id); - if (!(err == LFS2_ERR_NOENT && id != 0x3ff)) { + if (!(err == LFS2_ERR_NOENT && lfs2_path_islast(path))) { return (err < 0) ? err : LFS2_ERR_EXIST; } // check that name fits - lfs2_size_t nlen = strlen(path); + lfs2_size_t nlen = lfs2_path_namelen(path); if (nlen > lfs2->name_max) { return LFS2_ERR_NAMETOOLONG; } // build up new directory - lfs2_alloc_ack(lfs2); + lfs2_alloc_ckpoint(lfs2); lfs2_mdir_t dir; err = lfs2_dir_alloc(lfs2, &dir); if (err) { @@ -2660,7 +2717,7 @@ static int lfs2_rawmkdir(lfs2_t *lfs2, const char *path) { } #endif -static int lfs2_dir_rawopen(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path) { +static int lfs2_dir_open_(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path) { lfs2_stag_t tag = lfs2_dir_find(lfs2, &dir->m, &path, NULL); if (tag < 0) { return tag; @@ -2704,14 +2761,14 @@ static int lfs2_dir_rawopen(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path) { return 0; } -static int lfs2_dir_rawclose(lfs2_t *lfs2, lfs2_dir_t *dir) { +static int lfs2_dir_close_(lfs2_t *lfs2, lfs2_dir_t *dir) { // remove from list of mdirs lfs2_mlist_remove(lfs2, (struct lfs2_mlist *)dir); return 0; } -static int lfs2_dir_rawread(lfs2_t *lfs2, lfs2_dir_t *dir, struct lfs2_info *info) { +static int lfs2_dir_read_(lfs2_t *lfs2, lfs2_dir_t *dir, struct lfs2_info *info) { memset(info, 0, sizeof(*info)); // special offset for '.' and '..' @@ -2756,9 +2813,9 @@ static int lfs2_dir_rawread(lfs2_t *lfs2, lfs2_dir_t *dir, struct lfs2_info *inf return true; } -static int lfs2_dir_rawseek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off) { +static int lfs2_dir_seek_(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off) { // simply walk from head dir - int err = lfs2_dir_rawrewind(lfs2, dir); + int err = lfs2_dir_rewind_(lfs2, dir); if (err) { return err; } @@ -2793,12 +2850,12 @@ static int lfs2_dir_rawseek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off) { return 0; } -static lfs2_soff_t lfs2_dir_rawtell(lfs2_t *lfs2, lfs2_dir_t *dir) { +static lfs2_soff_t lfs2_dir_tell_(lfs2_t *lfs2, lfs2_dir_t *dir) { (void)lfs2; return dir->pos; } -static int lfs2_dir_rawrewind(lfs2_t *lfs2, lfs2_dir_t *dir) { +static int lfs2_dir_rewind_(lfs2_t *lfs2, lfs2_dir_t *dir) { // reload the head dir int err = lfs2_dir_fetch(lfs2, &dir->m, dir->head); if (err) { @@ -3004,7 +3061,7 @@ static int lfs2_ctz_traverse(lfs2_t *lfs2, /// Top level file operations /// -static int lfs2_file_rawopencfg(lfs2_t *lfs2, lfs2_file_t *file, +static int lfs2_file_opencfg_(lfs2_t *lfs2, lfs2_file_t *file, const char *path, int flags, const struct lfs2_file_config *cfg) { #ifndef LFS2_READONLY @@ -3029,7 +3086,7 @@ static int lfs2_file_rawopencfg(lfs2_t *lfs2, lfs2_file_t *file, // allocate entry for file if it doesn't exist lfs2_stag_t tag = lfs2_dir_find(lfs2, &file->m, &path, &file->id); - if (tag < 0 && !(tag == LFS2_ERR_NOENT && file->id != 0x3ff)) { + if (tag < 0 && !(tag == LFS2_ERR_NOENT && lfs2_path_islast(path))) { err = tag; goto cleanup; } @@ -3049,8 +3106,14 @@ static int lfs2_file_rawopencfg(lfs2_t *lfs2, lfs2_file_t *file, goto cleanup; } + // don't allow trailing slashes + if (lfs2_path_isdir(path)) { + err = LFS2_ERR_NOTDIR; + goto cleanup; + } + // check that name fits - lfs2_size_t nlen = strlen(path); + lfs2_size_t nlen = lfs2_path_namelen(path); if (nlen > lfs2->name_max) { err = LFS2_ERR_NAMETOOLONG; goto cleanup; @@ -3166,22 +3229,22 @@ static int lfs2_file_rawopencfg(lfs2_t *lfs2, lfs2_file_t *file, #ifndef LFS2_READONLY file->flags |= LFS2_F_ERRED; #endif - lfs2_file_rawclose(lfs2, file); + lfs2_file_close_(lfs2, file); return err; } #ifndef LFS2_NO_MALLOC -static int lfs2_file_rawopen(lfs2_t *lfs2, lfs2_file_t *file, +static int lfs2_file_open_(lfs2_t *lfs2, lfs2_file_t *file, const char *path, int flags) { static const struct lfs2_file_config defaults = {0}; - int err = lfs2_file_rawopencfg(lfs2, file, path, flags, &defaults); + int err = lfs2_file_opencfg_(lfs2, file, path, flags, &defaults); return err; } #endif -static int lfs2_file_rawclose(lfs2_t *lfs2, lfs2_file_t *file) { +static int lfs2_file_close_(lfs2_t *lfs2, lfs2_file_t *file) { #ifndef LFS2_READONLY - int err = lfs2_file_rawsync(lfs2, file); + int err = lfs2_file_sync_(lfs2, file); #else int err = 0; #endif @@ -3272,7 +3335,7 @@ static int lfs2_file_relocate(lfs2_t *lfs2, lfs2_file_t *file) { #ifndef LFS2_READONLY static int lfs2_file_outline(lfs2_t *lfs2, lfs2_file_t *file) { file->off = file->pos; - lfs2_alloc_ack(lfs2); + lfs2_alloc_ckpoint(lfs2); int err = lfs2_file_relocate(lfs2, file); if (err) { return err; @@ -3364,7 +3427,7 @@ static int lfs2_file_flush(lfs2_t *lfs2, lfs2_file_t *file) { } #ifndef LFS2_READONLY -static int lfs2_file_rawsync(lfs2_t *lfs2, lfs2_file_t *file) { +static int lfs2_file_sync_(lfs2_t *lfs2, lfs2_file_t *file) { if (file->flags & LFS2_F_ERRED) { // it's not safe to do anything if our file errored return 0; @@ -3379,6 +3442,15 @@ static int lfs2_file_rawsync(lfs2_t *lfs2, lfs2_file_t *file) { if ((file->flags & LFS2_F_DIRTY) && !lfs2_pair_isnull(file->m.pair)) { + // before we commit metadata, we need sync the disk to make sure + // data writes don't complete after metadata writes + if (!(file->flags & LFS2_F_INLINE)) { + err = lfs2_bd_sync(lfs2, &lfs2->pcache, &lfs2->rcache, false); + if (err) { + return err; + } + } + // update dir entry uint16_t type; const void *buffer; @@ -3477,7 +3549,7 @@ static lfs2_ssize_t lfs2_file_flushedread(lfs2_t *lfs2, lfs2_file_t *file, return size; } -static lfs2_ssize_t lfs2_file_rawread(lfs2_t *lfs2, lfs2_file_t *file, +static lfs2_ssize_t lfs2_file_read_(lfs2_t *lfs2, lfs2_file_t *file, void *buffer, lfs2_size_t size) { LFS2_ASSERT((file->flags & LFS2_O_RDONLY) == LFS2_O_RDONLY); @@ -3502,11 +3574,7 @@ static lfs2_ssize_t lfs2_file_flushedwrite(lfs2_t *lfs2, lfs2_file_t *file, lfs2_size_t nsize = size; if ((file->flags & LFS2_F_INLINE) && - lfs2_max(file->pos+nsize, file->ctz.size) > - lfs2_min(0x3fe, lfs2_min( - lfs2->cfg->cache_size, - (lfs2->cfg->metadata_max ? - lfs2->cfg->metadata_max : lfs2->cfg->block_size) / 8))) { + lfs2_max(file->pos+nsize, file->ctz.size) > lfs2->inline_max) { // inline file doesn't fit anymore int err = lfs2_file_outline(lfs2, file); if (err) { @@ -3535,7 +3603,7 @@ static lfs2_ssize_t lfs2_file_flushedwrite(lfs2_t *lfs2, lfs2_file_t *file, } // extend file with new blocks - lfs2_alloc_ack(lfs2); + lfs2_alloc_ckpoint(lfs2); int err = lfs2_ctz_extend(lfs2, &file->cache, &lfs2->rcache, file->block, file->pos, &file->block, &file->off); @@ -3578,13 +3646,13 @@ static lfs2_ssize_t lfs2_file_flushedwrite(lfs2_t *lfs2, lfs2_file_t *file, data += diff; nsize -= diff; - lfs2_alloc_ack(lfs2); + lfs2_alloc_ckpoint(lfs2); } return size; } -static lfs2_ssize_t lfs2_file_rawwrite(lfs2_t *lfs2, lfs2_file_t *file, +static lfs2_ssize_t lfs2_file_write_(lfs2_t *lfs2, lfs2_file_t *file, const void *buffer, lfs2_size_t size) { LFS2_ASSERT((file->flags & LFS2_O_WRONLY) == LFS2_O_WRONLY); @@ -3628,25 +3696,19 @@ static lfs2_ssize_t lfs2_file_rawwrite(lfs2_t *lfs2, lfs2_file_t *file, } #endif -static lfs2_soff_t lfs2_file_rawseek(lfs2_t *lfs2, lfs2_file_t *file, +static lfs2_soff_t lfs2_file_seek_(lfs2_t *lfs2, lfs2_file_t *file, lfs2_soff_t off, int whence) { // find new pos + // + // fortunately for us, littlefs is limited to 31-bit file sizes, so we + // don't have to worry too much about integer overflow lfs2_off_t npos = file->pos; if (whence == LFS2_SEEK_SET) { npos = off; } else if (whence == LFS2_SEEK_CUR) { - if ((lfs2_soff_t)file->pos + off < 0) { - return LFS2_ERR_INVAL; - } else { - npos = file->pos + off; - } + npos = file->pos + (lfs2_off_t)off; } else if (whence == LFS2_SEEK_END) { - lfs2_soff_t res = lfs2_file_rawsize(lfs2, file) + off; - if (res < 0) { - return LFS2_ERR_INVAL; - } else { - npos = res; - } + npos = (lfs2_off_t)lfs2_file_size_(lfs2, file) + (lfs2_off_t)off; } if (npos > lfs2->file_max) { @@ -3661,13 +3723,8 @@ static lfs2_soff_t lfs2_file_rawseek(lfs2_t *lfs2, lfs2_file_t *file, // if we're only reading and our new offset is still in the file's cache // we can avoid flushing and needing to reread the data - if ( -#ifndef LFS2_READONLY - !(file->flags & LFS2_F_WRITING) -#else - true -#endif - ) { + if ((file->flags & LFS2_F_READING) + && file->off != lfs2->cfg->block_size) { int oindex = lfs2_ctz_index(lfs2, &(lfs2_off_t){file->pos}); lfs2_off_t noff = npos; int nindex = lfs2_ctz_index(lfs2, &noff); @@ -3692,7 +3749,7 @@ static lfs2_soff_t lfs2_file_rawseek(lfs2_t *lfs2, lfs2_file_t *file, } #ifndef LFS2_READONLY -static int lfs2_file_rawtruncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t size) { +static int lfs2_file_truncate_(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t size) { LFS2_ASSERT((file->flags & LFS2_O_WRONLY) == LFS2_O_WRONLY); if (size > LFS2_FILE_MAX) { @@ -3700,15 +3757,12 @@ static int lfs2_file_rawtruncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t siz } lfs2_off_t pos = file->pos; - lfs2_off_t oldsize = lfs2_file_rawsize(lfs2, file); + lfs2_off_t oldsize = lfs2_file_size_(lfs2, file); if (size < oldsize) { // revert to inline file? - if (size <= lfs2_min(0x3fe, lfs2_min( - lfs2->cfg->cache_size, - (lfs2->cfg->metadata_max ? - lfs2->cfg->metadata_max : lfs2->cfg->block_size) / 8))) { + if (size <= lfs2->inline_max) { // flush+seek to head - lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, 0, LFS2_SEEK_SET); + lfs2_soff_t res = lfs2_file_seek_(lfs2, file, 0, LFS2_SEEK_SET); if (res < 0) { return (int)res; } @@ -3753,14 +3807,14 @@ static int lfs2_file_rawtruncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t siz } } else if (size > oldsize) { // flush+seek if not already at end - lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, 0, LFS2_SEEK_END); + lfs2_soff_t res = lfs2_file_seek_(lfs2, file, 0, LFS2_SEEK_END); if (res < 0) { return (int)res; } // fill with zeros while (file->pos < size) { - res = lfs2_file_rawwrite(lfs2, file, &(uint8_t){0}, 1); + res = lfs2_file_write_(lfs2, file, &(uint8_t){0}, 1); if (res < 0) { return (int)res; } @@ -3768,7 +3822,7 @@ static int lfs2_file_rawtruncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t siz } // restore pos - lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, pos, LFS2_SEEK_SET); + lfs2_soff_t res = lfs2_file_seek_(lfs2, file, pos, LFS2_SEEK_SET); if (res < 0) { return (int)res; } @@ -3777,13 +3831,13 @@ static int lfs2_file_rawtruncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t siz } #endif -static lfs2_soff_t lfs2_file_rawtell(lfs2_t *lfs2, lfs2_file_t *file) { +static lfs2_soff_t lfs2_file_tell_(lfs2_t *lfs2, lfs2_file_t *file) { (void)lfs2; return file->pos; } -static int lfs2_file_rawrewind(lfs2_t *lfs2, lfs2_file_t *file) { - lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, 0, LFS2_SEEK_SET); +static int lfs2_file_rewind_(lfs2_t *lfs2, lfs2_file_t *file) { + lfs2_soff_t res = lfs2_file_seek_(lfs2, file, 0, LFS2_SEEK_SET); if (res < 0) { return (int)res; } @@ -3791,7 +3845,7 @@ static int lfs2_file_rawrewind(lfs2_t *lfs2, lfs2_file_t *file) { return 0; } -static lfs2_soff_t lfs2_file_rawsize(lfs2_t *lfs2, lfs2_file_t *file) { +static lfs2_soff_t lfs2_file_size_(lfs2_t *lfs2, lfs2_file_t *file) { (void)lfs2; #ifndef LFS2_READONLY @@ -3805,18 +3859,24 @@ static lfs2_soff_t lfs2_file_rawsize(lfs2_t *lfs2, lfs2_file_t *file) { /// General fs operations /// -static int lfs2_rawstat(lfs2_t *lfs2, const char *path, struct lfs2_info *info) { +static int lfs2_stat_(lfs2_t *lfs2, const char *path, struct lfs2_info *info) { lfs2_mdir_t cwd; lfs2_stag_t tag = lfs2_dir_find(lfs2, &cwd, &path, NULL); if (tag < 0) { return (int)tag; } + // only allow trailing slashes on dirs + if (strchr(path, '/') != NULL + && lfs2_tag_type3(tag) != LFS2_TYPE_DIR) { + return LFS2_ERR_NOTDIR; + } + return lfs2_dir_getinfo(lfs2, &cwd, lfs2_tag_id(tag), info); } #ifndef LFS2_READONLY -static int lfs2_rawremove(lfs2_t *lfs2, const char *path) { +static int lfs2_remove_(lfs2_t *lfs2, const char *path) { // deorphan if we haven't yet, needed at most once after poweron int err = lfs2_fs_forceconsistency(lfs2); if (err) { @@ -3895,7 +3955,7 @@ static int lfs2_rawremove(lfs2_t *lfs2, const char *path) { #endif #ifndef LFS2_READONLY -static int lfs2_rawrename(lfs2_t *lfs2, const char *oldpath, const char *newpath) { +static int lfs2_rename_(lfs2_t *lfs2, const char *oldpath, const char *newpath) { // deorphan if we haven't yet, needed at most once after poweron int err = lfs2_fs_forceconsistency(lfs2); if (err) { @@ -3914,7 +3974,7 @@ static int lfs2_rawrename(lfs2_t *lfs2, const char *oldpath, const char *newpath uint16_t newid; lfs2_stag_t prevtag = lfs2_dir_find(lfs2, &newcwd, &newpath, &newid); if ((prevtag < 0 || lfs2_tag_id(prevtag) == 0x3ff) && - !(prevtag == LFS2_ERR_NOENT && newid != 0x3ff)) { + !(prevtag == LFS2_ERR_NOENT && lfs2_path_islast(newpath))) { return (prevtag < 0) ? (int)prevtag : LFS2_ERR_INVAL; } @@ -3925,8 +3985,14 @@ static int lfs2_rawrename(lfs2_t *lfs2, const char *oldpath, const char *newpath struct lfs2_mlist prevdir; prevdir.next = lfs2->mlist; if (prevtag == LFS2_ERR_NOENT) { + // if we're a file, don't allow trailing slashes + if (lfs2_path_isdir(newpath) + && lfs2_tag_type3(oldtag) != LFS2_TYPE_DIR) { + return LFS2_ERR_NOTDIR; + } + // check that name fits - lfs2_size_t nlen = strlen(newpath); + lfs2_size_t nlen = lfs2_path_namelen(newpath); if (nlen > lfs2->name_max) { return LFS2_ERR_NAMETOOLONG; } @@ -3938,7 +4004,9 @@ static int lfs2_rawrename(lfs2_t *lfs2, const char *oldpath, const char *newpath newoldid += 1; } } else if (lfs2_tag_type3(prevtag) != lfs2_tag_type3(oldtag)) { - return LFS2_ERR_ISDIR; + return (lfs2_tag_type3(prevtag) == LFS2_TYPE_DIR) + ? LFS2_ERR_ISDIR + : LFS2_ERR_NOTDIR; } else if (samepair && newid == newoldid) { // we're renaming to ourselves?? return 0; @@ -3984,7 +4052,8 @@ static int lfs2_rawrename(lfs2_t *lfs2, const char *oldpath, const char *newpath {LFS2_MKTAG_IF(prevtag != LFS2_ERR_NOENT, LFS2_TYPE_DELETE, newid, 0), NULL}, {LFS2_MKTAG(LFS2_TYPE_CREATE, newid, 0), NULL}, - {LFS2_MKTAG(lfs2_tag_type3(oldtag), newid, strlen(newpath)), newpath}, + {LFS2_MKTAG(lfs2_tag_type3(oldtag), + newid, lfs2_path_namelen(newpath)), newpath}, {LFS2_MKTAG(LFS2_FROM_MOVE, newid, lfs2_tag_id(oldtag)), &oldcwd}, {LFS2_MKTAG_IF(samepair, LFS2_TYPE_DELETE, newoldid, 0), NULL})); @@ -4030,7 +4099,7 @@ static int lfs2_rawrename(lfs2_t *lfs2, const char *oldpath, const char *newpath } #endif -static lfs2_ssize_t lfs2_rawgetattr(lfs2_t *lfs2, const char *path, +static lfs2_ssize_t lfs2_getattr_(lfs2_t *lfs2, const char *path, uint8_t type, void *buffer, lfs2_size_t size) { lfs2_mdir_t cwd; lfs2_stag_t tag = lfs2_dir_find(lfs2, &cwd, &path, NULL); @@ -4088,7 +4157,7 @@ static int lfs2_commitattr(lfs2_t *lfs2, const char *path, #endif #ifndef LFS2_READONLY -static int lfs2_rawsetattr(lfs2_t *lfs2, const char *path, +static int lfs2_setattr_(lfs2_t *lfs2, const char *path, uint8_t type, const void *buffer, lfs2_size_t size) { if (size > lfs2->attr_max) { return LFS2_ERR_NOSPC; @@ -4099,13 +4168,28 @@ static int lfs2_rawsetattr(lfs2_t *lfs2, const char *path, #endif #ifndef LFS2_READONLY -static int lfs2_rawremoveattr(lfs2_t *lfs2, const char *path, uint8_t type) { +static int lfs2_removeattr_(lfs2_t *lfs2, const char *path, uint8_t type) { return lfs2_commitattr(lfs2, path, type, NULL, 0x3ff); } #endif /// Filesystem operations /// + +// compile time checks, see lfs2.h for why these limits exist +#if LFS2_NAME_MAX > 1022 +#error "Invalid LFS2_NAME_MAX, must be <= 1022" +#endif + +#if LFS2_FILE_MAX > 2147483647 +#error "Invalid LFS2_FILE_MAX, must be <= 2147483647" +#endif + +#if LFS2_ATTR_MAX > 1022 +#error "Invalid LFS2_ATTR_MAX, must be <= 1022" +#endif + +// common filesystem initialization static int lfs2_init(lfs2_t *lfs2, const struct lfs2_config *cfg) { lfs2->cfg = cfg; lfs2->block_count = cfg->block_count; // May be 0 @@ -4126,6 +4210,14 @@ static int lfs2_init(lfs2_t *lfs2, const struct lfs2_config *cfg) { // which littlefs currently does not support LFS2_ASSERT((bool)0x80000000); + // check that the required io functions are provided + LFS2_ASSERT(lfs2->cfg->read != NULL); +#ifndef LFS2_READONLY + LFS2_ASSERT(lfs2->cfg->prog != NULL); + LFS2_ASSERT(lfs2->cfg->erase != NULL); + LFS2_ASSERT(lfs2->cfg->sync != NULL); +#endif + // validate that the lfs2-cfg sizes were initiated properly before // performing any arithmetic logics with them LFS2_ASSERT(lfs2->cfg->read_size != 0); @@ -4153,6 +4245,23 @@ static int lfs2_init(lfs2_t *lfs2, const struct lfs2_config *cfg) { // wear-leveling. LFS2_ASSERT(lfs2->cfg->block_cycles != 0); + // check that compact_thresh makes sense + // + // metadata can't be compacted below block_size/2, and metadata can't + // exceed a block_size + LFS2_ASSERT(lfs2->cfg->compact_thresh == 0 + || lfs2->cfg->compact_thresh >= lfs2->cfg->block_size/2); + LFS2_ASSERT(lfs2->cfg->compact_thresh == (lfs2_size_t)-1 + || lfs2->cfg->compact_thresh <= lfs2->cfg->block_size); + + // check that metadata_max is a multiple of read_size and prog_size, + // and a factor of the block_size + LFS2_ASSERT(!lfs2->cfg->metadata_max + || lfs2->cfg->metadata_max % lfs2->cfg->read_size == 0); + LFS2_ASSERT(!lfs2->cfg->metadata_max + || lfs2->cfg->metadata_max % lfs2->cfg->prog_size == 0); + LFS2_ASSERT(!lfs2->cfg->metadata_max + || lfs2->cfg->block_size % lfs2->cfg->metadata_max == 0); // setup read cache if (lfs2->cfg->read_buffer) { @@ -4180,15 +4289,14 @@ static int lfs2_init(lfs2_t *lfs2, const struct lfs2_config *cfg) { lfs2_cache_zero(lfs2, &lfs2->rcache); lfs2_cache_zero(lfs2, &lfs2->pcache); - // setup lookahead, must be multiple of 64-bits, 32-bit aligned + // setup lookahead buffer, note mount finishes initializing this after + // we establish a decent pseudo-random seed LFS2_ASSERT(lfs2->cfg->lookahead_size > 0); - LFS2_ASSERT(lfs2->cfg->lookahead_size % 8 == 0 && - (uintptr_t)lfs2->cfg->lookahead_buffer % 4 == 0); if (lfs2->cfg->lookahead_buffer) { - lfs2->free.buffer = lfs2->cfg->lookahead_buffer; + lfs2->lookahead.buffer = lfs2->cfg->lookahead_buffer; } else { - lfs2->free.buffer = lfs2_malloc(lfs2->cfg->lookahead_size); - if (!lfs2->free.buffer) { + lfs2->lookahead.buffer = lfs2_malloc(lfs2->cfg->lookahead_size); + if (!lfs2->lookahead.buffer) { err = LFS2_ERR_NOMEM; goto cleanup; } @@ -4215,6 +4323,27 @@ static int lfs2_init(lfs2_t *lfs2, const struct lfs2_config *cfg) { LFS2_ASSERT(lfs2->cfg->metadata_max <= lfs2->cfg->block_size); + LFS2_ASSERT(lfs2->cfg->inline_max == (lfs2_size_t)-1 + || lfs2->cfg->inline_max <= lfs2->cfg->cache_size); + LFS2_ASSERT(lfs2->cfg->inline_max == (lfs2_size_t)-1 + || lfs2->cfg->inline_max <= lfs2->attr_max); + LFS2_ASSERT(lfs2->cfg->inline_max == (lfs2_size_t)-1 + || lfs2->cfg->inline_max <= ((lfs2->cfg->metadata_max) + ? lfs2->cfg->metadata_max + : lfs2->cfg->block_size)/8); + lfs2->inline_max = lfs2->cfg->inline_max; + if (lfs2->inline_max == (lfs2_size_t)-1) { + lfs2->inline_max = 0; + } else if (lfs2->inline_max == 0) { + lfs2->inline_max = lfs2_min( + lfs2->cfg->cache_size, + lfs2_min( + lfs2->attr_max, + ((lfs2->cfg->metadata_max) + ? lfs2->cfg->metadata_max + : lfs2->cfg->block_size)/8)); + } + // setup default state lfs2->root[0] = LFS2_BLOCK_NULL; lfs2->root[1] = LFS2_BLOCK_NULL; @@ -4245,7 +4374,7 @@ static int lfs2_deinit(lfs2_t *lfs2) { } if (!lfs2->cfg->lookahead_buffer) { - lfs2_free(lfs2->free.buffer); + lfs2_free(lfs2->lookahead.buffer); } return 0; @@ -4254,7 +4383,7 @@ static int lfs2_deinit(lfs2_t *lfs2) { #ifndef LFS2_READONLY -static int lfs2_rawformat(lfs2_t *lfs2, const struct lfs2_config *cfg) { +static int lfs2_format_(lfs2_t *lfs2, const struct lfs2_config *cfg) { int err = 0; { err = lfs2_init(lfs2, cfg); @@ -4265,12 +4394,12 @@ static int lfs2_rawformat(lfs2_t *lfs2, const struct lfs2_config *cfg) { LFS2_ASSERT(cfg->block_count != 0); // create free lookahead - memset(lfs2->free.buffer, 0, lfs2->cfg->lookahead_size); - lfs2->free.off = 0; - lfs2->free.size = lfs2_min(8*lfs2->cfg->lookahead_size, + memset(lfs2->lookahead.buffer, 0, lfs2->cfg->lookahead_size); + lfs2->lookahead.start = 0; + lfs2->lookahead.size = lfs2_min(8*lfs2->cfg->lookahead_size, lfs2->block_count); - lfs2->free.i = 0; - lfs2_alloc_ack(lfs2); + lfs2->lookahead.next = 0; + lfs2_alloc_ckpoint(lfs2); // create root dir lfs2_mdir_t root; @@ -4321,7 +4450,31 @@ static int lfs2_rawformat(lfs2_t *lfs2, const struct lfs2_config *cfg) { } #endif -static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { +struct lfs2_tortoise_t { + lfs2_block_t pair[2]; + lfs2_size_t i; + lfs2_size_t period; +}; + +static int lfs2_tortoise_detectcycles( + const lfs2_mdir_t *dir, struct lfs2_tortoise_t *tortoise) { + // detect cycles with Brent's algorithm + if (lfs2_pair_issync(dir->tail, tortoise->pair)) { + LFS2_WARN("Cycle detected in tail list"); + return LFS2_ERR_CORRUPT; + } + if (tortoise->i == tortoise->period) { + tortoise->pair[0] = dir->tail[0]; + tortoise->pair[1] = dir->tail[1]; + tortoise->i = 0; + tortoise->period *= 2; + } + tortoise->i += 1; + + return LFS2_ERR_OK; +} + +static int lfs2_mount_(lfs2_t *lfs2, const struct lfs2_config *cfg) { int err = lfs2_init(lfs2, cfg); if (err) { return err; @@ -4329,23 +4482,16 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { // scan directory blocks for superblock and any global updates lfs2_mdir_t dir = {.tail = {0, 1}}; - lfs2_block_t tortoise[2] = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}; - lfs2_size_t tortoise_i = 1; - lfs2_size_t tortoise_period = 1; + struct lfs2_tortoise_t tortoise = { + .pair = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}, + .i = 1, + .period = 1, + }; while (!lfs2_pair_isnull(dir.tail)) { - // detect cycles with Brent's algorithm - if (lfs2_pair_issync(dir.tail, tortoise)) { - LFS2_WARN("Cycle detected in tail list"); - err = LFS2_ERR_CORRUPT; + err = lfs2_tortoise_detectcycles(&dir, &tortoise); + if (err < 0) { goto cleanup; } - if (tortoise_i == tortoise_period) { - tortoise[0] = dir.tail[0]; - tortoise[1] = dir.tail[1]; - tortoise_i = 0; - tortoise_period *= 2; - } - tortoise_i += 1; // fetch next block in tail list lfs2_stag_t tag = lfs2_dir_fetchmatch(lfs2, &dir, dir.tail, @@ -4394,6 +4540,7 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { // found older minor version? set an in-device only bit in the // gstate so we know we need to rewrite the superblock before // the first write + bool needssuperblock = false; if (minor_version < lfs2_fs_disk_version_minor(lfs2)) { LFS2_DEBUG("Found older minor version " "v%"PRIu16".%"PRIu16" < v%"PRIu16".%"PRIu16, @@ -4401,10 +4548,11 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { minor_version, lfs2_fs_disk_version_major(lfs2), lfs2_fs_disk_version_minor(lfs2)); - // note this bit is reserved on disk, so fetching more gstate - // will not interfere here - lfs2_fs_prepsuperblock(lfs2, true); + needssuperblock = true; } + // note this bit is reserved on disk, so fetching more gstate + // will not interfere here + lfs2_fs_prepsuperblock(lfs2, needssuperblock); // check superblock configuration if (superblock.name_max) { @@ -4438,6 +4586,9 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { } lfs2->attr_max = superblock.attr_max; + + // we also need to update inline_max in case attr_max changed + lfs2->inline_max = lfs2_min(lfs2->inline_max, lfs2->attr_max); } // this is where we get the block_count from disk if block_count=0 @@ -4478,23 +4629,23 @@ static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { // setup free lookahead, to distribute allocations uniformly across // boots, we start the allocator at a random location - lfs2->free.off = lfs2->seed % lfs2->block_count; + lfs2->lookahead.start = lfs2->seed % lfs2->block_count; lfs2_alloc_drop(lfs2); return 0; cleanup: - lfs2_rawunmount(lfs2); + lfs2_unmount_(lfs2); return err; } -static int lfs2_rawunmount(lfs2_t *lfs2) { +static int lfs2_unmount_(lfs2_t *lfs2) { return lfs2_deinit(lfs2); } /// Filesystem filesystem operations /// -static int lfs2_fs_rawstat(lfs2_t *lfs2, struct lfs2_fsinfo *fsinfo) { +static int lfs2_fs_stat_(lfs2_t *lfs2, struct lfs2_fsinfo *fsinfo) { // if the superblock is up-to-date, we must be on the most recent // minor version of littlefs if (!lfs2_gstate_needssuperblock(&lfs2->gstate)) { @@ -4534,7 +4685,7 @@ static int lfs2_fs_rawstat(lfs2_t *lfs2, struct lfs2_fsinfo *fsinfo) { return 0; } -int lfs2_fs_rawtraverse(lfs2_t *lfs2, +int lfs2_fs_traverse_(lfs2_t *lfs2, int (*cb)(void *data, lfs2_block_t block), void *data, bool includeorphans) { // iterate over metadata pairs @@ -4553,22 +4704,17 @@ int lfs2_fs_rawtraverse(lfs2_t *lfs2, } #endif - lfs2_block_t tortoise[2] = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}; - lfs2_size_t tortoise_i = 1; - lfs2_size_t tortoise_period = 1; + struct lfs2_tortoise_t tortoise = { + .pair = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}, + .i = 1, + .period = 1, + }; + int err = LFS2_ERR_OK; while (!lfs2_pair_isnull(dir.tail)) { - // detect cycles with Brent's algorithm - if (lfs2_pair_issync(dir.tail, tortoise)) { - LFS2_WARN("Cycle detected in tail list"); + err = lfs2_tortoise_detectcycles(&dir, &tortoise); + if (err < 0) { return LFS2_ERR_CORRUPT; } - if (tortoise_i == tortoise_period) { - tortoise[0] = dir.tail[0]; - tortoise[1] = dir.tail[1]; - tortoise_i = 0; - tortoise_period *= 2; - } - tortoise_i += 1; for (int i = 0; i < 2; i++) { int err = cb(data, dir.tail[i]); @@ -4647,22 +4793,17 @@ static int lfs2_fs_pred(lfs2_t *lfs2, // iterate over all directory directory entries pdir->tail[0] = 0; pdir->tail[1] = 1; - lfs2_block_t tortoise[2] = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}; - lfs2_size_t tortoise_i = 1; - lfs2_size_t tortoise_period = 1; + struct lfs2_tortoise_t tortoise = { + .pair = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}, + .i = 1, + .period = 1, + }; + int err = LFS2_ERR_OK; while (!lfs2_pair_isnull(pdir->tail)) { - // detect cycles with Brent's algorithm - if (lfs2_pair_issync(pdir->tail, tortoise)) { - LFS2_WARN("Cycle detected in tail list"); + err = lfs2_tortoise_detectcycles(pdir, &tortoise); + if (err < 0) { return LFS2_ERR_CORRUPT; } - if (tortoise_i == tortoise_period) { - tortoise[0] = pdir->tail[0]; - tortoise[1] = pdir->tail[1]; - tortoise_i = 0; - tortoise_period *= 2; - } - tortoise_i += 1; if (lfs2_pair_cmp(pdir->tail, pair) == 0) { return 0; @@ -4712,22 +4853,17 @@ static lfs2_stag_t lfs2_fs_parent(lfs2_t *lfs2, const lfs2_block_t pair[2], // use fetchmatch with callback to find pairs parent->tail[0] = 0; parent->tail[1] = 1; - lfs2_block_t tortoise[2] = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}; - lfs2_size_t tortoise_i = 1; - lfs2_size_t tortoise_period = 1; + struct lfs2_tortoise_t tortoise = { + .pair = {LFS2_BLOCK_NULL, LFS2_BLOCK_NULL}, + .i = 1, + .period = 1, + }; + int err = LFS2_ERR_OK; while (!lfs2_pair_isnull(parent->tail)) { - // detect cycles with Brent's algorithm - if (lfs2_pair_issync(parent->tail, tortoise)) { - LFS2_WARN("Cycle detected in tail list"); - return LFS2_ERR_CORRUPT; - } - if (tortoise_i == tortoise_period) { - tortoise[0] = parent->tail[0]; - tortoise[1] = parent->tail[1]; - tortoise_i = 0; - tortoise_period *= 2; + err = lfs2_tortoise_detectcycles(parent, &tortoise); + if (err < 0) { + return err; } - tortoise_i += 1; lfs2_stag_t tag = lfs2_dir_fetchmatch(lfs2, parent, parent->tail, LFS2_MKTAG(0x7ff, 0, 0x3ff), @@ -4999,7 +5135,7 @@ static int lfs2_fs_forceconsistency(lfs2_t *lfs2) { #endif #ifndef LFS2_READONLY -static int lfs2_fs_rawmkconsistent(lfs2_t *lfs2) { +static int lfs2_fs_mkconsistent_(lfs2_t *lfs2) { // lfs2_fs_forceconsistency does most of the work here int err = lfs2_fs_forceconsistency(lfs2); if (err) { @@ -5035,9 +5171,9 @@ static int lfs2_fs_size_count(void *p, lfs2_block_t block) { return 0; } -static lfs2_ssize_t lfs2_fs_rawsize(lfs2_t *lfs2) { +static lfs2_ssize_t lfs2_fs_size_(lfs2_t *lfs2) { lfs2_size_t size = 0; - int err = lfs2_fs_rawtraverse(lfs2, lfs2_fs_size_count, &size, false); + int err = lfs2_fs_traverse_(lfs2, lfs2_fs_size_count, &size, false); if (err) { return err; } @@ -5045,8 +5181,59 @@ static lfs2_ssize_t lfs2_fs_rawsize(lfs2_t *lfs2) { return size; } +// explicit garbage collection #ifndef LFS2_READONLY -static int lfs2_fs_rawgrow(lfs2_t *lfs2, lfs2_size_t block_count) { +static int lfs2_fs_gc_(lfs2_t *lfs2) { + // force consistency, even if we're not necessarily going to write, + // because this function is supposed to take care of janitorial work + // isn't it? + int err = lfs2_fs_forceconsistency(lfs2); + if (err) { + return err; + } + + // try to compact metadata pairs, note we can't really accomplish + // anything if compact_thresh doesn't at least leave a prog_size + // available + if (lfs2->cfg->compact_thresh + < lfs2->cfg->block_size - lfs2->cfg->prog_size) { + // iterate over all mdirs + lfs2_mdir_t mdir = {.tail = {0, 1}}; + while (!lfs2_pair_isnull(mdir.tail)) { + err = lfs2_dir_fetch(lfs2, &mdir, mdir.tail); + if (err) { + return err; + } + + // not erased? exceeds our compaction threshold? + if (!mdir.erased || ((lfs2->cfg->compact_thresh == 0) + ? mdir.off > lfs2->cfg->block_size - lfs2->cfg->block_size/8 + : mdir.off > lfs2->cfg->compact_thresh)) { + // the easiest way to trigger a compaction is to mark + // the mdir as unerased and add an empty commit + mdir.erased = false; + err = lfs2_dir_commit(lfs2, &mdir, NULL, 0); + if (err) { + return err; + } + } + } + } + + // try to populate the lookahead buffer, unless it's already full + if (lfs2->lookahead.size < 8*lfs2->cfg->lookahead_size) { + err = lfs2_alloc_scan(lfs2); + if (err) { + return err; + } + } + + return 0; +} +#endif + +#ifndef LFS2_READONLY +static int lfs2_fs_grow_(lfs2_t *lfs2, lfs2_size_t block_count) { // shrinking is not supported LFS2_ASSERT(block_count >= lfs2->block_count); @@ -5451,10 +5638,10 @@ static int lfs21_mount(lfs2_t *lfs2, struct lfs21 *lfs21, lfs2->lfs21->root[1] = LFS2_BLOCK_NULL; // setup free lookahead - lfs2->free.off = 0; - lfs2->free.size = 0; - lfs2->free.i = 0; - lfs2_alloc_ack(lfs2); + lfs2->lookahead.start = 0; + lfs2->lookahead.size = 0; + lfs2->lookahead.next = 0; + lfs2_alloc_ckpoint(lfs2); // load superblock lfs21_dir_t dir; @@ -5505,7 +5692,7 @@ static int lfs21_unmount(lfs2_t *lfs2) { } /// v1 migration /// -static int lfs2_rawmigrate(lfs2_t *lfs2, const struct lfs2_config *cfg) { +static int lfs2_migrate_(lfs2_t *lfs2, const struct lfs2_config *cfg) { struct lfs21 lfs21; // Indeterminate filesystem size not allowed for migration. @@ -5759,7 +5946,7 @@ int lfs2_format(lfs2_t *lfs2, const struct lfs2_config *cfg) { ".read=%p, .prog=%p, .erase=%p, .sync=%p, " ".read_size=%"PRIu32", .prog_size=%"PRIu32", " ".block_size=%"PRIu32", .block_count=%"PRIu32", " - ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " + ".block_cycles=%"PRId32", .cache_size=%"PRIu32", " ".lookahead_size=%"PRIu32", .read_buffer=%p, " ".prog_buffer=%p, .lookahead_buffer=%p, " ".name_max=%"PRIu32", .file_max=%"PRIu32", " @@ -5772,7 +5959,7 @@ int lfs2_format(lfs2_t *lfs2, const struct lfs2_config *cfg) { cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, cfg->name_max, cfg->file_max, cfg->attr_max); - err = lfs2_rawformat(lfs2, cfg); + err = lfs2_format_(lfs2, cfg); LFS2_TRACE("lfs2_format -> %d", err); LFS2_UNLOCK(cfg); @@ -5789,7 +5976,7 @@ int lfs2_mount(lfs2_t *lfs2, const struct lfs2_config *cfg) { ".read=%p, .prog=%p, .erase=%p, .sync=%p, " ".read_size=%"PRIu32", .prog_size=%"PRIu32", " ".block_size=%"PRIu32", .block_count=%"PRIu32", " - ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " + ".block_cycles=%"PRId32", .cache_size=%"PRIu32", " ".lookahead_size=%"PRIu32", .read_buffer=%p, " ".prog_buffer=%p, .lookahead_buffer=%p, " ".name_max=%"PRIu32", .file_max=%"PRIu32", " @@ -5802,7 +5989,7 @@ int lfs2_mount(lfs2_t *lfs2, const struct lfs2_config *cfg) { cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, cfg->name_max, cfg->file_max, cfg->attr_max); - err = lfs2_rawmount(lfs2, cfg); + err = lfs2_mount_(lfs2, cfg); LFS2_TRACE("lfs2_mount -> %d", err); LFS2_UNLOCK(cfg); @@ -5816,7 +6003,7 @@ int lfs2_unmount(lfs2_t *lfs2) { } LFS2_TRACE("lfs2_unmount(%p)", (void*)lfs2); - err = lfs2_rawunmount(lfs2); + err = lfs2_unmount_(lfs2); LFS2_TRACE("lfs2_unmount -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -5831,7 +6018,7 @@ int lfs2_remove(lfs2_t *lfs2, const char *path) { } LFS2_TRACE("lfs2_remove(%p, \"%s\")", (void*)lfs2, path); - err = lfs2_rawremove(lfs2, path); + err = lfs2_remove_(lfs2, path); LFS2_TRACE("lfs2_remove -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -5847,7 +6034,7 @@ int lfs2_rename(lfs2_t *lfs2, const char *oldpath, const char *newpath) { } LFS2_TRACE("lfs2_rename(%p, \"%s\", \"%s\")", (void*)lfs2, oldpath, newpath); - err = lfs2_rawrename(lfs2, oldpath, newpath); + err = lfs2_rename_(lfs2, oldpath, newpath); LFS2_TRACE("lfs2_rename -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -5862,7 +6049,7 @@ int lfs2_stat(lfs2_t *lfs2, const char *path, struct lfs2_info *info) { } LFS2_TRACE("lfs2_stat(%p, \"%s\", %p)", (void*)lfs2, path, (void*)info); - err = lfs2_rawstat(lfs2, path, info); + err = lfs2_stat_(lfs2, path, info); LFS2_TRACE("lfs2_stat -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -5878,7 +6065,7 @@ lfs2_ssize_t lfs2_getattr(lfs2_t *lfs2, const char *path, LFS2_TRACE("lfs2_getattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", (void*)lfs2, path, type, buffer, size); - lfs2_ssize_t res = lfs2_rawgetattr(lfs2, path, type, buffer, size); + lfs2_ssize_t res = lfs2_getattr_(lfs2, path, type, buffer, size); LFS2_TRACE("lfs2_getattr -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); @@ -5895,7 +6082,7 @@ int lfs2_setattr(lfs2_t *lfs2, const char *path, LFS2_TRACE("lfs2_setattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", (void*)lfs2, path, type, buffer, size); - err = lfs2_rawsetattr(lfs2, path, type, buffer, size); + err = lfs2_setattr_(lfs2, path, type, buffer, size); LFS2_TRACE("lfs2_setattr -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -5911,7 +6098,7 @@ int lfs2_removeattr(lfs2_t *lfs2, const char *path, uint8_t type) { } LFS2_TRACE("lfs2_removeattr(%p, \"%s\", %"PRIu8")", (void*)lfs2, path, type); - err = lfs2_rawremoveattr(lfs2, path, type); + err = lfs2_removeattr_(lfs2, path, type); LFS2_TRACE("lfs2_removeattr -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -5926,10 +6113,10 @@ int lfs2_file_open(lfs2_t *lfs2, lfs2_file_t *file, const char *path, int flags) return err; } LFS2_TRACE("lfs2_file_open(%p, %p, \"%s\", %x)", - (void*)lfs2, (void*)file, path, flags); + (void*)lfs2, (void*)file, path, (unsigned)flags); LFS2_ASSERT(!lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); - err = lfs2_file_rawopen(lfs2, file, path, flags); + err = lfs2_file_open_(lfs2, file, path, flags); LFS2_TRACE("lfs2_file_open -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -5946,11 +6133,11 @@ int lfs2_file_opencfg(lfs2_t *lfs2, lfs2_file_t *file, } LFS2_TRACE("lfs2_file_opencfg(%p, %p, \"%s\", %x, %p {" ".buffer=%p, .attrs=%p, .attr_count=%"PRIu32"})", - (void*)lfs2, (void*)file, path, flags, + (void*)lfs2, (void*)file, path, (unsigned)flags, (void*)cfg, cfg->buffer, (void*)cfg->attrs, cfg->attr_count); LFS2_ASSERT(!lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); - err = lfs2_file_rawopencfg(lfs2, file, path, flags, cfg); + err = lfs2_file_opencfg_(lfs2, file, path, flags, cfg); LFS2_TRACE("lfs2_file_opencfg -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -5965,7 +6152,7 @@ int lfs2_file_close(lfs2_t *lfs2, lfs2_file_t *file) { LFS2_TRACE("lfs2_file_close(%p, %p)", (void*)lfs2, (void*)file); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); - err = lfs2_file_rawclose(lfs2, file); + err = lfs2_file_close_(lfs2, file); LFS2_TRACE("lfs2_file_close -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -5981,7 +6168,7 @@ int lfs2_file_sync(lfs2_t *lfs2, lfs2_file_t *file) { LFS2_TRACE("lfs2_file_sync(%p, %p)", (void*)lfs2, (void*)file); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); - err = lfs2_file_rawsync(lfs2, file); + err = lfs2_file_sync_(lfs2, file); LFS2_TRACE("lfs2_file_sync -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -5999,7 +6186,7 @@ lfs2_ssize_t lfs2_file_read(lfs2_t *lfs2, lfs2_file_t *file, (void*)lfs2, (void*)file, buffer, size); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); - lfs2_ssize_t res = lfs2_file_rawread(lfs2, file, buffer, size); + lfs2_ssize_t res = lfs2_file_read_(lfs2, file, buffer, size); LFS2_TRACE("lfs2_file_read -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); @@ -6017,7 +6204,7 @@ lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, (void*)lfs2, (void*)file, buffer, size); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); - lfs2_ssize_t res = lfs2_file_rawwrite(lfs2, file, buffer, size); + lfs2_ssize_t res = lfs2_file_write_(lfs2, file, buffer, size); LFS2_TRACE("lfs2_file_write -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); @@ -6035,7 +6222,7 @@ lfs2_soff_t lfs2_file_seek(lfs2_t *lfs2, lfs2_file_t *file, (void*)lfs2, (void*)file, off, whence); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); - lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, off, whence); + lfs2_soff_t res = lfs2_file_seek_(lfs2, file, off, whence); LFS2_TRACE("lfs2_file_seek -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); @@ -6052,7 +6239,7 @@ int lfs2_file_truncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t size) { (void*)lfs2, (void*)file, size); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); - err = lfs2_file_rawtruncate(lfs2, file, size); + err = lfs2_file_truncate_(lfs2, file, size); LFS2_TRACE("lfs2_file_truncate -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6068,7 +6255,7 @@ lfs2_soff_t lfs2_file_tell(lfs2_t *lfs2, lfs2_file_t *file) { LFS2_TRACE("lfs2_file_tell(%p, %p)", (void*)lfs2, (void*)file); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); - lfs2_soff_t res = lfs2_file_rawtell(lfs2, file); + lfs2_soff_t res = lfs2_file_tell_(lfs2, file); LFS2_TRACE("lfs2_file_tell -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); @@ -6082,7 +6269,7 @@ int lfs2_file_rewind(lfs2_t *lfs2, lfs2_file_t *file) { } LFS2_TRACE("lfs2_file_rewind(%p, %p)", (void*)lfs2, (void*)file); - err = lfs2_file_rawrewind(lfs2, file); + err = lfs2_file_rewind_(lfs2, file); LFS2_TRACE("lfs2_file_rewind -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6097,9 +6284,9 @@ lfs2_soff_t lfs2_file_size(lfs2_t *lfs2, lfs2_file_t *file) { LFS2_TRACE("lfs2_file_size(%p, %p)", (void*)lfs2, (void*)file); LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); - lfs2_soff_t res = lfs2_file_rawsize(lfs2, file); + lfs2_soff_t res = lfs2_file_size_(lfs2, file); - LFS2_TRACE("lfs2_file_size -> %"PRId32, res); + LFS2_TRACE("lfs2_file_size -> %"PRIu32, res); LFS2_UNLOCK(lfs2->cfg); return res; } @@ -6112,7 +6299,7 @@ int lfs2_mkdir(lfs2_t *lfs2, const char *path) { } LFS2_TRACE("lfs2_mkdir(%p, \"%s\")", (void*)lfs2, path); - err = lfs2_rawmkdir(lfs2, path); + err = lfs2_mkdir_(lfs2, path); LFS2_TRACE("lfs2_mkdir -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6128,7 +6315,7 @@ int lfs2_dir_open(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path) { LFS2_TRACE("lfs2_dir_open(%p, %p, \"%s\")", (void*)lfs2, (void*)dir, path); LFS2_ASSERT(!lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)dir)); - err = lfs2_dir_rawopen(lfs2, dir, path); + err = lfs2_dir_open_(lfs2, dir, path); LFS2_TRACE("lfs2_dir_open -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6142,7 +6329,7 @@ int lfs2_dir_close(lfs2_t *lfs2, lfs2_dir_t *dir) { } LFS2_TRACE("lfs2_dir_close(%p, %p)", (void*)lfs2, (void*)dir); - err = lfs2_dir_rawclose(lfs2, dir); + err = lfs2_dir_close_(lfs2, dir); LFS2_TRACE("lfs2_dir_close -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6157,7 +6344,7 @@ int lfs2_dir_read(lfs2_t *lfs2, lfs2_dir_t *dir, struct lfs2_info *info) { LFS2_TRACE("lfs2_dir_read(%p, %p, %p)", (void*)lfs2, (void*)dir, (void*)info); - err = lfs2_dir_rawread(lfs2, dir, info); + err = lfs2_dir_read_(lfs2, dir, info); LFS2_TRACE("lfs2_dir_read -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6172,7 +6359,7 @@ int lfs2_dir_seek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off) { LFS2_TRACE("lfs2_dir_seek(%p, %p, %"PRIu32")", (void*)lfs2, (void*)dir, off); - err = lfs2_dir_rawseek(lfs2, dir, off); + err = lfs2_dir_seek_(lfs2, dir, off); LFS2_TRACE("lfs2_dir_seek -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6186,7 +6373,7 @@ lfs2_soff_t lfs2_dir_tell(lfs2_t *lfs2, lfs2_dir_t *dir) { } LFS2_TRACE("lfs2_dir_tell(%p, %p)", (void*)lfs2, (void*)dir); - lfs2_soff_t res = lfs2_dir_rawtell(lfs2, dir); + lfs2_soff_t res = lfs2_dir_tell_(lfs2, dir); LFS2_TRACE("lfs2_dir_tell -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); @@ -6200,7 +6387,7 @@ int lfs2_dir_rewind(lfs2_t *lfs2, lfs2_dir_t *dir) { } LFS2_TRACE("lfs2_dir_rewind(%p, %p)", (void*)lfs2, (void*)dir); - err = lfs2_dir_rawrewind(lfs2, dir); + err = lfs2_dir_rewind_(lfs2, dir); LFS2_TRACE("lfs2_dir_rewind -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6214,7 +6401,7 @@ int lfs2_fs_stat(lfs2_t *lfs2, struct lfs2_fsinfo *fsinfo) { } LFS2_TRACE("lfs2_fs_stat(%p, %p)", (void*)lfs2, (void*)fsinfo); - err = lfs2_fs_rawstat(lfs2, fsinfo); + err = lfs2_fs_stat_(lfs2, fsinfo); LFS2_TRACE("lfs2_fs_stat -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6228,7 +6415,7 @@ lfs2_ssize_t lfs2_fs_size(lfs2_t *lfs2) { } LFS2_TRACE("lfs2_fs_size(%p)", (void*)lfs2); - lfs2_ssize_t res = lfs2_fs_rawsize(lfs2); + lfs2_ssize_t res = lfs2_fs_size_(lfs2); LFS2_TRACE("lfs2_fs_size -> %"PRId32, res); LFS2_UNLOCK(lfs2->cfg); @@ -6243,7 +6430,7 @@ int lfs2_fs_traverse(lfs2_t *lfs2, int (*cb)(void *, lfs2_block_t), void *data) LFS2_TRACE("lfs2_fs_traverse(%p, %p, %p)", (void*)lfs2, (void*)(uintptr_t)cb, data); - err = lfs2_fs_rawtraverse(lfs2, cb, data, true); + err = lfs2_fs_traverse_(lfs2, cb, data, true); LFS2_TRACE("lfs2_fs_traverse -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6251,32 +6438,32 @@ int lfs2_fs_traverse(lfs2_t *lfs2, int (*cb)(void *, lfs2_block_t), void *data) } #ifndef LFS2_READONLY -int lfs2_fs_gc(lfs2_t *lfs2) { +int lfs2_fs_mkconsistent(lfs2_t *lfs2) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } - LFS2_TRACE("lfs2_fs_gc(%p)", (void*)lfs2); + LFS2_TRACE("lfs2_fs_mkconsistent(%p)", (void*)lfs2); - err = lfs2_fs_rawgc(lfs2); + err = lfs2_fs_mkconsistent_(lfs2); - LFS2_TRACE("lfs2_fs_gc -> %d", err); + LFS2_TRACE("lfs2_fs_mkconsistent -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } #endif #ifndef LFS2_READONLY -int lfs2_fs_mkconsistent(lfs2_t *lfs2) { +int lfs2_fs_gc(lfs2_t *lfs2) { int err = LFS2_LOCK(lfs2->cfg); if (err) { return err; } - LFS2_TRACE("lfs2_fs_mkconsistent(%p)", (void*)lfs2); + LFS2_TRACE("lfs2_fs_gc(%p)", (void*)lfs2); - err = lfs2_fs_rawmkconsistent(lfs2); + err = lfs2_fs_gc_(lfs2); - LFS2_TRACE("lfs2_fs_mkconsistent -> %d", err); + LFS2_TRACE("lfs2_fs_gc -> %d", err); LFS2_UNLOCK(lfs2->cfg); return err; } @@ -6290,7 +6477,7 @@ int lfs2_fs_grow(lfs2_t *lfs2, lfs2_size_t block_count) { } LFS2_TRACE("lfs2_fs_grow(%p, %"PRIu32")", (void*)lfs2, block_count); - err = lfs2_fs_rawgrow(lfs2, block_count); + err = lfs2_fs_grow_(lfs2, block_count); LFS2_TRACE("lfs2_fs_grow -> %d", err); LFS2_UNLOCK(lfs2->cfg); @@ -6308,7 +6495,7 @@ int lfs2_migrate(lfs2_t *lfs2, const struct lfs2_config *cfg) { ".read=%p, .prog=%p, .erase=%p, .sync=%p, " ".read_size=%"PRIu32", .prog_size=%"PRIu32", " ".block_size=%"PRIu32", .block_count=%"PRIu32", " - ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " + ".block_cycles=%"PRId32", .cache_size=%"PRIu32", " ".lookahead_size=%"PRIu32", .read_buffer=%p, " ".prog_buffer=%p, .lookahead_buffer=%p, " ".name_max=%"PRIu32", .file_max=%"PRIu32", " @@ -6321,7 +6508,7 @@ int lfs2_migrate(lfs2_t *lfs2, const struct lfs2_config *cfg) { cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, cfg->name_max, cfg->file_max, cfg->attr_max); - err = lfs2_rawmigrate(lfs2, cfg); + err = lfs2_migrate_(lfs2, cfg); LFS2_TRACE("lfs2_migrate -> %d", err); LFS2_UNLOCK(cfg); diff --git a/lib/littlefs/lfs2.h b/lib/littlefs/lfs2.h index 4c426fc4c7096..f503fd00bcfa2 100644 --- a/lib/littlefs/lfs2.h +++ b/lib/littlefs/lfs2.h @@ -21,7 +21,7 @@ extern "C" // Software library version // Major (top-nibble), incremented on backwards incompatible changes // Minor (bottom-nibble), incremented on feature additions -#define LFS2_VERSION 0x00020008 +#define LFS2_VERSION 0x0002000a #define LFS2_VERSION_MAJOR (0xffff & (LFS2_VERSION >> 16)) #define LFS2_VERSION_MINOR (0xffff & (LFS2_VERSION >> 0)) @@ -52,16 +52,15 @@ typedef uint32_t lfs2_block_t; #endif // Maximum size of a file in bytes, may be redefined to limit to support other -// drivers. Limited on disk to <= 4294967296. However, above 2147483647 the -// functions lfs2_file_seek, lfs2_file_size, and lfs2_file_tell will return -// incorrect values due to using signed integers. Stored in superblock and -// must be respected by other littlefs drivers. +// drivers. Limited on disk to <= 2147483647. Stored in superblock and must be +// respected by other littlefs drivers. #ifndef LFS2_FILE_MAX #define LFS2_FILE_MAX 2147483647 #endif // Maximum size of custom attributes in bytes, may be redefined, but there is -// no real benefit to using a smaller LFS2_ATTR_MAX. Limited to <= 1022. +// no real benefit to using a smaller LFS2_ATTR_MAX. Limited to <= 1022. Stored +// in superblock and must be respected by other littlefs drivers. #ifndef LFS2_ATTR_MAX #define LFS2_ATTR_MAX 1022 #endif @@ -205,7 +204,8 @@ struct lfs2_config { // program sizes. lfs2_size_t block_size; - // Number of erasable blocks on the device. + // Number of erasable blocks on the device. Defaults to block_count stored + // on disk when zero. lfs2_size_t block_count; // Number of erase cycles before littlefs evicts metadata logs and moves @@ -226,9 +226,20 @@ struct lfs2_config { // Size of the lookahead buffer in bytes. A larger lookahead buffer // increases the number of blocks found during an allocation pass. The // lookahead buffer is stored as a compact bitmap, so each byte of RAM - // can track 8 blocks. Must be a multiple of 8. + // can track 8 blocks. lfs2_size_t lookahead_size; + // Threshold for metadata compaction during lfs2_fs_gc in bytes. Metadata + // pairs that exceed this threshold will be compacted during lfs2_fs_gc. + // Defaults to ~88% block_size when zero, though the default may change + // in the future. + // + // Note this only affects lfs2_fs_gc. Normal compactions still only occur + // when full. + // + // Set to -1 to disable metadata compaction during lfs2_fs_gc. + lfs2_size_t compact_thresh; + // Optional statically allocated read buffer. Must be cache_size. // By default lfs2_malloc is used to allocate this buffer. void *read_buffer; @@ -237,25 +248,24 @@ struct lfs2_config { // By default lfs2_malloc is used to allocate this buffer. void *prog_buffer; - // Optional statically allocated lookahead buffer. Must be lookahead_size - // and aligned to a 32-bit boundary. By default lfs2_malloc is used to - // allocate this buffer. + // Optional statically allocated lookahead buffer. Must be lookahead_size. + // By default lfs2_malloc is used to allocate this buffer. void *lookahead_buffer; // Optional upper limit on length of file names in bytes. No downside for // larger names except the size of the info struct which is controlled by - // the LFS2_NAME_MAX define. Defaults to LFS2_NAME_MAX when zero. Stored in - // superblock and must be respected by other littlefs drivers. + // the LFS2_NAME_MAX define. Defaults to LFS2_NAME_MAX or name_max stored on + // disk when zero. lfs2_size_t name_max; // Optional upper limit on files in bytes. No downside for larger files - // but must be <= LFS2_FILE_MAX. Defaults to LFS2_FILE_MAX when zero. Stored - // in superblock and must be respected by other littlefs drivers. + // but must be <= LFS2_FILE_MAX. Defaults to LFS2_FILE_MAX or file_max stored + // on disk when zero. lfs2_size_t file_max; // Optional upper limit on custom attributes in bytes. No downside for // larger attributes size but must be <= LFS2_ATTR_MAX. Defaults to - // LFS2_ATTR_MAX when zero. + // LFS2_ATTR_MAX or attr_max stored on disk when zero. lfs2_size_t attr_max; // Optional upper limit on total space given to metadata pairs in bytes. On @@ -264,6 +274,15 @@ struct lfs2_config { // Defaults to block_size when zero. lfs2_size_t metadata_max; + // Optional upper limit on inlined files in bytes. Inlined files live in + // metadata and decrease storage requirements, but may be limited to + // improve metadata-related performance. Must be <= cache_size, <= + // attr_max, and <= block_size/8. Defaults to the largest possible + // inline_max when zero. + // + // Set to -1 to disable inlined files. + lfs2_size_t inline_max; + #ifdef LFS2_MULTIVERSION // On-disk version to use when writing in the form of 16-bit major version // + 16-bit minor version. This limiting metadata to what is supported by @@ -430,19 +449,20 @@ typedef struct lfs2 { lfs2_gstate_t gdisk; lfs2_gstate_t gdelta; - struct lfs2_free { - lfs2_block_t off; + struct lfs2_lookahead { + lfs2_block_t start; lfs2_block_t size; - lfs2_block_t i; - lfs2_block_t ack; - uint32_t *buffer; - } free; + lfs2_block_t next; + lfs2_block_t ckpoint; + uint8_t *buffer; + } lookahead; const struct lfs2_config *cfg; lfs2_size_t block_count; lfs2_size_t name_max; lfs2_size_t file_max; lfs2_size_t attr_max; + lfs2_size_t inline_max; #ifdef LFS2_MIGRATE struct lfs21 *lfs21; @@ -712,18 +732,6 @@ lfs2_ssize_t lfs2_fs_size(lfs2_t *lfs2); // Returns a negative error code on failure. int lfs2_fs_traverse(lfs2_t *lfs2, int (*cb)(void*, lfs2_block_t), void *data); -// Attempt to proactively find free blocks -// -// Calling this function is not required, but may allowing the offloading of -// the expensive block allocation scan to a less time-critical code path. -// -// Note: littlefs currently does not persist any found free blocks to disk. -// This may change in the future. -// -// Returns a negative error code on failure. Finding no free blocks is -// not an error. -int lfs2_fs_gc(lfs2_t *lfs2); - #ifndef LFS2_READONLY // Attempt to make the filesystem consistent and ready for writing // @@ -736,6 +744,24 @@ int lfs2_fs_gc(lfs2_t *lfs2); int lfs2_fs_mkconsistent(lfs2_t *lfs2); #endif +#ifndef LFS2_READONLY +// Attempt any janitorial work +// +// This currently: +// 1. Calls mkconsistent if not already consistent +// 2. Compacts metadata > compact_thresh +// 3. Populates the block allocator +// +// Though additional janitorial work may be added in the future. +// +// Calling this function is not required, but may allow the offloading of +// expensive janitorial work to a less time-critical code path. +// +// Returns a negative error code on failure. Accomplishing nothing is not +// an error. +int lfs2_fs_gc(lfs2_t *lfs2); +#endif + #ifndef LFS2_READONLY // Grows the filesystem to a new size, updating the superblock with the new // block count. diff --git a/lib/littlefs/lfs2_util.c b/lib/littlefs/lfs2_util.c index c9850e78869c3..4fe7e5340ce77 100644 --- a/lib/littlefs/lfs2_util.c +++ b/lib/littlefs/lfs2_util.c @@ -11,6 +11,8 @@ #ifndef LFS2_CONFIG +// If user provides their own CRC impl we don't need this +#ifndef LFS2_CRC // Software CRC implementation with small lookup table uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size) { static const uint32_t rtable[16] = { @@ -29,6 +31,7 @@ uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size) { return crc; } +#endif #endif diff --git a/lib/littlefs/lfs2_util.h b/lib/littlefs/lfs2_util.h index dd2cbcc106d84..48d9f4c572a0c 100644 --- a/lib/littlefs/lfs2_util.h +++ b/lib/littlefs/lfs2_util.h @@ -8,6 +8,9 @@ #ifndef LFS2_UTIL_H #define LFS2_UTIL_H +#define LFS2_STRINGIZE(x) LFS2_STRINGIZE2(x) +#define LFS2_STRINGIZE2(x) #x + // Users can override lfs2_util.h with their own configuration by defining // LFS2_CONFIG as a header file to include (-DLFS2_CONFIG=lfs2_config.h). // @@ -15,11 +18,26 @@ // provided by the config file. To start, I would suggest copying lfs2_util.h // and modifying as needed. #ifdef LFS2_CONFIG -#define LFS2_STRINGIZE(x) LFS2_STRINGIZE2(x) -#define LFS2_STRINGIZE2(x) #x #include LFS2_STRINGIZE(LFS2_CONFIG) #else +// Alternatively, users can provide a header file which defines +// macros and other things consumed by littlefs. +// +// For example, provide my_defines.h, which contains +// something like: +// +// #include +// extern void *my_malloc(size_t sz); +// #define LFS2_MALLOC(sz) my_malloc(sz) +// +// And build littlefs with the header by defining LFS2_DEFINES. +// (-DLFS2_DEFINES=my_defines.h) + +#ifdef LFS2_DEFINES +#include LFS2_STRINGIZE(LFS2_DEFINES) +#endif + // System includes #include #include @@ -212,12 +230,22 @@ static inline uint32_t lfs2_tobe32(uint32_t a) { } // Calculate CRC-32 with polynomial = 0x04c11db7 +#ifdef LFS2_CRC +uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size) { + return LFS2_CRC(crc, buffer, size) +} +#else uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size); +#endif // Allocate memory, only used if buffers are not provided to littlefs -// Note, memory must be 64-bit aligned +// +// littlefs current has no alignment requirements, as it only allocates +// byte-level buffers. static inline void *lfs2_malloc(size_t size) { -#ifndef LFS2_NO_MALLOC +#if defined(LFS2_MALLOC) + return LFS2_MALLOC(size); +#elif !defined(LFS2_NO_MALLOC) return malloc(size); #else (void)size; @@ -227,7 +255,9 @@ static inline void *lfs2_malloc(size_t size) { // Deallocate memory, only used if buffers are not provided to littlefs static inline void lfs2_free(void *p) { -#ifndef LFS2_NO_MALLOC +#if defined(LFS2_FREE) + LFS2_FREE(p); +#elif !defined(LFS2_NO_MALLOC) free(p); #else (void)p; diff --git a/tests/extmod/vfs_lfs.py b/tests/extmod/vfs_lfs.py index 3ad57fd9c38f1..40d58e9c9f743 100644 --- a/tests/extmod/vfs_lfs.py +++ b/tests/extmod/vfs_lfs.py @@ -136,7 +136,7 @@ def test(bdev, vfs_class): print(fs.getcwd()) fs.chdir("../testdir") print(fs.getcwd()) - fs.chdir("../..") + fs.chdir("..") print(fs.getcwd()) fs.chdir(".//testdir") print(fs.getcwd()) From 2b29b1b8f9dc7014c27d9df43ca0ebd3ccc86060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Wed, 16 Apr 2025 18:26:37 +0200 Subject: [PATCH 070/154] lib/littlefs: Reuse existing CRC32 function to save space. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Getting this to work required fixing a small issue in `lfs2_util.h`, which has been submitted upstream. Signed-off-by: DaniĆ«l van de Giessen --- extmod/extmod.mk | 2 +- extmod/littlefs-include/lfs2_defines.h | 12 ++++++++++++ lib/littlefs/lfs2_util.h | 4 ++-- 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 extmod/littlefs-include/lfs2_defines.h diff --git a/extmod/extmod.mk b/extmod/extmod.mk index b2a0f490b6e10..997dd3ba98ead 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -206,7 +206,7 @@ endif ifeq ($(MICROPY_VFS_LFS2),1) CFLAGS_EXTMOD += -DMICROPY_VFS_LFS2=1 -CFLAGS_THIRDPARTY += -DLFS2_NO_MALLOC -DLFS2_NO_DEBUG -DLFS2_NO_WARN -DLFS2_NO_ERROR -DLFS2_NO_ASSERT +CFLAGS_THIRDPARTY += -DLFS2_NO_MALLOC -DLFS2_NO_DEBUG -DLFS2_NO_WARN -DLFS2_NO_ERROR -DLFS2_NO_ASSERT -DLFS2_DEFINES=extmod/littlefs-include/lfs2_defines.h SRC_THIRDPARTY_C += $(addprefix $(LITTLEFS_DIR)/,\ lfs2.c \ lfs2_util.c \ diff --git a/extmod/littlefs-include/lfs2_defines.h b/extmod/littlefs-include/lfs2_defines.h new file mode 100644 index 0000000000000..4ae566f508afa --- /dev/null +++ b/extmod/littlefs-include/lfs2_defines.h @@ -0,0 +1,12 @@ +#ifndef LFS2_DEFINES_H +#define LFS2_DEFINES_H + +#include "py/mpconfig.h" + +#if MICROPY_PY_DEFLATE +// We reuse the CRC32 implementation from uzlib to save a few bytes +#include "lib/uzlib/uzlib.h" +#define LFS2_CRC(crc, buffer, size) uzlib_crc32(buffer, size, crc) +#endif + +#endif \ No newline at end of file diff --git a/lib/littlefs/lfs2_util.h b/lib/littlefs/lfs2_util.h index 48d9f4c572a0c..3b191f6885f44 100644 --- a/lib/littlefs/lfs2_util.h +++ b/lib/littlefs/lfs2_util.h @@ -231,8 +231,8 @@ static inline uint32_t lfs2_tobe32(uint32_t a) { // Calculate CRC-32 with polynomial = 0x04c11db7 #ifdef LFS2_CRC -uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size) { - return LFS2_CRC(crc, buffer, size) +static inline uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size) { + return LFS2_CRC(crc, buffer, size); } #else uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size); From aedaa405953f6fad29ecd5e16d10de126ecb1af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Wed, 16 Apr 2025 14:17:14 +0200 Subject: [PATCH 071/154] py/modthread: Initialize thread state nlr_top to NULL. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This ensures the check in MP_NLR_JUMP_HEAD works as expected and nlr_jump_fail gets called so we get a bit better error message. Signed-off-by: DaniĆ«l van de Giessen --- py/runtime.h | 1 + 1 file changed, 1 insertion(+) diff --git a/py/runtime.h b/py/runtime.h index 77cdd0e203da9..ffbc3972a32dc 100644 --- a/py/runtime.h +++ b/py/runtime.h @@ -165,6 +165,7 @@ static inline void mp_thread_init_state(mp_state_thread_t *ts, size_t stack_size ts->gc_lock_depth = 0; // There are no pending jump callbacks or exceptions yet + ts->nlr_top = NULL; ts->nlr_jump_callback_top = NULL; ts->mp_pending_exception = MP_OBJ_NULL; From 1b123579a2c987901c3ddb9c342b041f1526943f Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Wed, 9 Apr 2025 13:39:51 +0200 Subject: [PATCH 072/154] py/makeversionhdr.py: Change utcfromtimestamp() to fromtimestamp(). The former is deprecated. Signed-off-by: Jos Verlinde --- py/makeversionhdr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py/makeversionhdr.py b/py/makeversionhdr.py index cb7325e416cb4..406a061a0948a 100644 --- a/py/makeversionhdr.py +++ b/py/makeversionhdr.py @@ -117,8 +117,8 @@ def make_version_header(repo_path, filename): build_date = datetime.date.today() if "SOURCE_DATE_EPOCH" in os.environ: - build_date = datetime.datetime.utcfromtimestamp( - int(os.environ["SOURCE_DATE_EPOCH"]) + build_date = datetime.datetime.fromtimestamp( + int(os.environ["SOURCE_DATE_EPOCH"]), datetime.timezone.utc ).date() # Generate the file with the git and version info From fe28cd78fefde7da1bc7614982223ce9e522143a Mon Sep 17 00:00:00 2001 From: danicampora Date: Thu, 3 Oct 2024 09:50:15 +0200 Subject: [PATCH 073/154] zephyr/modbluetooth_zephyr: Allow BLE to create services at runtime. This commit adds the required functionality for a peripheral to create services at runtime, using `BLE.register_services()`. The feature is enabled on the nrf52840dk_nrf52840 board. Note that the `CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION=n` option must be used so that BLE notifications/indications can be sent even if not subscribed. Signed-off-by: danicampora --- ports/zephyr/boards/nrf52840dk_nrf52840.conf | 5 + ports/zephyr/modbluetooth_zephyr.c | 611 ++++++++++++++++++- 2 files changed, 604 insertions(+), 12 deletions(-) diff --git a/ports/zephyr/boards/nrf52840dk_nrf52840.conf b/ports/zephyr/boards/nrf52840dk_nrf52840.conf index e9ec78b64ffae..dc2ed1c4b49df 100644 --- a/ports/zephyr/boards/nrf52840dk_nrf52840.conf +++ b/ports/zephyr/boards/nrf52840dk_nrf52840.conf @@ -1,8 +1,13 @@ CONFIG_NETWORKING=n CONFIG_BT=y CONFIG_BT_DEVICE_NAME_DYNAMIC=y +CONFIG_BT_GATT_DYNAMIC_DB=y CONFIG_BT_PERIPHERAL=y CONFIG_BT_CENTRAL=y +CONFIG_BT_GATT_CLIENT=y +CONFIG_BT_L2CAP_TX_MTU=252 +CONFIG_BT_BUF_ACL_RX_SIZE=256 +CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION=n CONFIG_MICROPY_HEAP_SIZE=98304 CONFIG_MAIN_STACK_SIZE=8192 diff --git a/ports/zephyr/modbluetooth_zephyr.c b/ports/zephyr/modbluetooth_zephyr.c index 8947bdf53567d..cdbeb7fc353ff 100644 --- a/ports/zephyr/modbluetooth_zephyr.c +++ b/ports/zephyr/modbluetooth_zephyr.c @@ -31,8 +31,12 @@ #if MICROPY_PY_BLUETOOTH +#include #include #include +#include +#include +#include #include "extmod/modbluetooth.h" #define DEBUG_printf(...) // printk("BLE: " __VA_ARGS__) @@ -44,6 +48,23 @@ #define ERRNO_BLUETOOTH_NOT_ACTIVE MP_ENODEV +#define MP_BLUETOOTH_ZEPHYR_MAX_SERVICES (8) + +/* This masks Permission bits from GATT API */ +#define GATT_PERM_MASK (BT_GATT_PERM_READ | \ + BT_GATT_PERM_READ_AUTHEN | \ + BT_GATT_PERM_READ_ENCRYPT | \ + BT_GATT_PERM_WRITE | \ + BT_GATT_PERM_WRITE_AUTHEN | \ + BT_GATT_PERM_WRITE_ENCRYPT | \ + BT_GATT_PERM_PREPARE_WRITE) + +#define GATT_PERM_ENC_READ_MASK (BT_GATT_PERM_READ_ENCRYPT | \ + BT_GATT_PERM_READ_AUTHEN) + +#define GATT_PERM_ENC_WRITE_MASK (BT_GATT_PERM_WRITE_ENCRYPT | \ + BT_GATT_PERM_WRITE_AUTHEN) + enum { MP_BLUETOOTH_ZEPHYR_BLE_STATE_OFF, MP_BLUETOOTH_ZEPHYR_BLE_STATE_ACTIVE, @@ -56,9 +77,42 @@ enum { MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_ACTIVE, }; +union uuid_u { + struct bt_uuid uuid; + struct bt_uuid_16 u16; + struct bt_uuid_32 u32; + struct bt_uuid_128 u128; +}; + +struct add_characteristic { + uint8_t properties; + uint8_t permissions; + const struct bt_uuid *uuid; +}; + +struct add_descriptor { + uint8_t permissions; + const struct bt_uuid *uuid; +}; + +typedef struct _mp_bt_zephyr_conn_t { + struct bt_conn *conn; + struct _mp_bt_zephyr_conn_t *next; +} mp_bt_zephyr_conn_t; + typedef struct _mp_bluetooth_zephyr_root_pointers_t { + // list of objects to be tracked by the gc + mp_obj_t objs_list; + // Characteristic (and descriptor) value storage. mp_gatts_db_t gatts_db; + + // Service definitions. + size_t n_services; + struct bt_gatt_service *services[MP_BLUETOOTH_ZEPHYR_MAX_SERVICES]; + + // active connections + mp_bt_zephyr_conn_t *connections; } mp_bluetooth_zephyr_root_pointers_t; static int mp_bluetooth_zephyr_ble_state; @@ -69,6 +123,98 @@ static struct k_timer mp_bluetooth_zephyr_gap_scan_timer; static struct bt_le_scan_cb mp_bluetooth_zephyr_gap_scan_cb_struct; #endif +static struct bt_data bt_ad_data[8]; +static size_t bt_ad_len = 0; +static struct bt_data bt_sd_data[8]; +static size_t bt_sd_len = 0; + +static mp_bt_zephyr_conn_t *mp_bt_zephyr_next_conn; + +static mp_bt_zephyr_conn_t *mp_bt_zephyr_find_connection(uint8_t conn_handle); +static void mp_bt_zephyr_insert_connection(mp_bt_zephyr_conn_t *connection); +static void mp_bt_zephyr_remove_connection(uint8_t conn_handle); +static void mp_bt_zephyr_connected(struct bt_conn *connected, uint8_t err); +static void mp_bt_zephyr_disconnected(struct bt_conn *disconn, uint8_t reason); +static struct bt_uuid *create_zephyr_uuid(const mp_obj_bluetooth_uuid_t *uuid); +static void gatt_db_add(const struct bt_gatt_attr *pattern, struct bt_gatt_attr *attr, size_t user_data_len); +static void add_service(const struct bt_uuid *u, struct bt_gatt_attr *attr); +static void add_characteristic(struct add_characteristic *ch, struct bt_gatt_attr *attr_chrc, struct bt_gatt_attr *attr_value); +static void add_ccc(struct bt_gatt_attr *attr, struct bt_gatt_attr *attr_desc); +static void add_cep(const struct bt_gatt_attr *attr_chrc, struct bt_gatt_attr *attr_desc); +static void add_descriptor(struct bt_gatt_attr *chrc, struct add_descriptor *d, struct bt_gatt_attr *attr_desc); +static void mp_bt_zephyr_gatt_indicate_done(struct bt_conn *conn, struct bt_gatt_indicate_params *params, uint8_t err); +static struct bt_gatt_attr *mp_bt_zephyr_find_attr_by_handle(uint16_t value_handle); + +static struct bt_conn_cb mp_bt_zephyr_conn_callbacks = { + .connected = mp_bt_zephyr_connected, + .disconnected = mp_bt_zephyr_disconnected, +}; + +static mp_bt_zephyr_conn_t *mp_bt_zephyr_find_connection(uint8_t conn_handle) { + struct bt_conn_info info; + for (mp_bt_zephyr_conn_t *connection = MP_STATE_PORT(bluetooth_zephyr_root_pointers)->connections; connection != NULL; connection = connection->next) { + if (connection->conn) { + bt_conn_get_info(connection->conn, &info); + if (info.id == conn_handle) { + return connection; + } + } + } + return NULL; +} + +static void mp_bt_zephyr_insert_connection(mp_bt_zephyr_conn_t *connection) { + connection->next = MP_STATE_PORT(bluetooth_zephyr_root_pointers)->connections; + MP_STATE_PORT(bluetooth_zephyr_root_pointers)->connections = connection; +} + +static void mp_bt_zephyr_remove_connection(uint8_t conn_handle) { + struct bt_conn_info info; + mp_bt_zephyr_conn_t *prev = NULL; + for (mp_bt_zephyr_conn_t *connection = MP_STATE_PORT(bluetooth_zephyr_root_pointers)->connections; connection != NULL; connection = connection->next) { + if (connection->conn) { + bt_conn_get_info(connection->conn, &info); + if (info.id == conn_handle) { + // unlink this item and the gc will eventually collect it + if (prev != NULL) { + prev->next = connection->next; + } else { + // move the start pointer + MP_STATE_PORT(bluetooth_zephyr_root_pointers)->connections = connection->next; + } + break; + } else { + prev = connection; + } + } + } +} + +static void mp_bt_zephyr_connected(struct bt_conn *conn, uint8_t err) { + struct bt_conn_info info; + bt_conn_get_info(conn, &info); + + if (err) { + uint8_t addr[6] = {0}; + DEBUG_printf("Connection from central failed (err %u)\n", err); + mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT, info.id, 0xff, addr); + } else { + DEBUG_printf("Central connected with id %d\n", info.id); + mp_bt_zephyr_next_conn->conn = bt_conn_ref(conn); + mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_CENTRAL_CONNECT, info.id, info.le.dst->type, info.le.dst->a.val); + mp_bt_zephyr_insert_connection(mp_bt_zephyr_next_conn); + } +} + +static void mp_bt_zephyr_disconnected(struct bt_conn *conn, uint8_t reason) { + struct bt_conn_info info; + bt_conn_get_info(conn, &info); + DEBUG_printf("Central disconnected (id %d reason %u)\n", info.id, reason); + bt_conn_unref(conn); + mp_bt_zephyr_remove_connection(info.id); + mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT, info.id, info.le.dst->type, info.le.dst->a.val); +} + static int bt_err_to_errno(int err) { // Zephyr uses errno codes directly, but they are negative. return -err; @@ -128,6 +274,11 @@ int mp_bluetooth_init(void) { MP_STATE_PORT(bluetooth_zephyr_root_pointers) = m_new0(mp_bluetooth_zephyr_root_pointers_t, 1); mp_bluetooth_gatts_db_create(&MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db); + MP_STATE_PORT(bluetooth_zephyr_root_pointers)->connections = NULL; + mp_bt_zephyr_next_conn = NULL; + + MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list = mp_obj_new_list(0, NULL); + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE mp_bluetooth_zephyr_gap_scan_state = MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_INACTIVE; k_timer_init(&mp_bluetooth_zephyr_gap_scan_timer, gap_scan_cb_timeout, NULL); @@ -137,6 +288,9 @@ int mp_bluetooth_init(void) { #endif if (mp_bluetooth_zephyr_ble_state == MP_BLUETOOTH_ZEPHYR_BLE_STATE_OFF) { + + bt_conn_cb_register(&mp_bt_zephyr_conn_callbacks); + // bt_enable can only be called once. int ret = bt_enable(NULL); if (ret) { @@ -160,6 +314,13 @@ void mp_bluetooth_deinit(void) { mp_bluetooth_gap_advertise_stop(); + #if CONFIG_BT_GATT_DYNAMIC_DB + for (size_t i = 0; i < MP_STATE_PORT(bluetooth_zephyr_root_pointers)->n_services; ++i) { + bt_gatt_service_unregister(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i]); + MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i] = NULL; + } + #endif + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE mp_bluetooth_gap_scan_stop(); bt_le_scan_cb_unregister(&mp_bluetooth_zephyr_gap_scan_cb_struct); @@ -170,6 +331,7 @@ void mp_bluetooth_deinit(void) { mp_bluetooth_zephyr_ble_state = MP_BLUETOOTH_ZEPHYR_BLE_STATE_SUSPENDED; MP_STATE_PORT(bluetooth_zephyr_root_pointers) = NULL; + mp_bt_zephyr_next_conn = NULL; } bool mp_bluetooth_is_active(void) { @@ -191,7 +353,7 @@ void mp_bluetooth_get_current_address(uint8_t *addr_type, uint8_t *addr) { } void mp_bluetooth_set_address_mode(uint8_t addr_mode) { - // TODO: implement + mp_raise_OSError(MP_EOPNOTSUPP); } size_t mp_bluetooth_gap_get_device_name(const uint8_t **buf) { @@ -232,15 +394,11 @@ int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, cons mp_bluetooth_gap_advertise_stop(); - struct bt_data bt_ad_data[8]; - size_t bt_ad_len = 0; if (adv_data) { bt_ad_len = MP_ARRAY_SIZE(bt_ad_data); mp_bluetooth_prepare_bt_data(adv_data, adv_data_len, bt_ad_data, &bt_ad_len); } - struct bt_data bt_sd_data[8]; - size_t bt_sd_len = 0; if (sr_data) { bt_sd_len = MP_ARRAY_SIZE(bt_sd_data); mp_bluetooth_prepare_bt_data(sr_data, sr_data_len, bt_sd_data, &bt_sd_len); @@ -259,6 +417,10 @@ int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, cons .peer = NULL, }; + // pre-allocate a new connection structure as we cannot allocate this inside the connection callback + mp_bt_zephyr_next_conn = m_new0(mp_bt_zephyr_conn_t, 1); + mp_obj_list_append(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list, MP_OBJ_FROM_PTR(mp_bt_zephyr_next_conn)); + return bt_err_to_errno(bt_le_adv_start(¶m, bt_ad_data, bt_ad_len, bt_sd_data, bt_sd_len)); } @@ -271,6 +433,8 @@ void mp_bluetooth_gap_advertise_stop(void) { } int mp_bluetooth_gatts_register_service_begin(bool append) { + #if CONFIG_BT_GATT_DYNAMIC_DB + if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } @@ -280,25 +444,190 @@ int mp_bluetooth_gatts_register_service_begin(bool append) { return MP_EOPNOTSUPP; } + // Unregister and unref any previous service definitions. + for (size_t i = 0; i < MP_STATE_PORT(bluetooth_zephyr_root_pointers)->n_services; ++i) { + bt_gatt_service_unregister(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i]); + MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i] = NULL; + } + MP_STATE_PORT(bluetooth_zephyr_root_pointers)->n_services = 0; + // Reset the gatt characteristic value db. mp_bluetooth_gatts_db_reset(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db); + MP_STATE_PORT(bluetooth_zephyr_root_pointers)->connections = NULL; + MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list = mp_obj_new_list(0, NULL); + mp_bt_zephyr_next_conn = NULL; + + return 0; + #else return MP_EOPNOTSUPP; + #endif } int mp_bluetooth_gatts_register_service_end(void) { - return MP_EOPNOTSUPP; + return 0; } int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, mp_obj_bluetooth_uuid_t **characteristic_uuids, uint16_t *characteristic_flags, mp_obj_bluetooth_uuid_t **descriptor_uuids, uint16_t *descriptor_flags, uint8_t *num_descriptors, uint16_t *handles, size_t num_characteristics) { + #if CONFIG_BT_GATT_DYNAMIC_DB + if (MP_STATE_PORT(bluetooth_zephyr_root_pointers)->n_services >= MP_BLUETOOTH_ZEPHYR_MAX_SERVICES) { + return MP_E2BIG; + } + + // first of all allocate the entire memory for all the attributes that this service is composed of + // 1 for the service itself, 2 for each characteristic (the declaration and the value), and one for each descriptor + size_t total_descriptors = 0; + for (size_t i = 0; i < num_characteristics; ++i) { + total_descriptors += num_descriptors[i]; + // we have to add the CCC manually + if (characteristic_flags[i] & (MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY | MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE)) { + total_descriptors += 1; + } + } + size_t total_attributes = 1 + (num_characteristics * 2) + total_descriptors; + + // allocate one extra so that we can know later where the final attribute is + struct bt_gatt_attr *svc_attributes = m_new(struct bt_gatt_attr, total_attributes + 1); + mp_obj_list_append(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list, MP_OBJ_FROM_PTR(svc_attributes)); + + size_t handle_index = 0; + size_t descriptor_index = 0; + size_t attr_index = 0; + // bitfield of the handles we should ignore, should be more than enough for most applications + uint64_t attrs_to_ignore = 0; + uint64_t attrs_are_chrs = 0; + uint64_t chr_has_ccc = 0; + + add_service(create_zephyr_uuid(service_uuid), &svc_attributes[attr_index]); + attr_index += 1; + + for (size_t i = 0; i < num_characteristics; ++i) { + + struct add_characteristic add_char; + add_char.uuid = create_zephyr_uuid(characteristic_uuids[i]); + add_char.permissions = 0; + add_char.properties = 0; + if (characteristic_flags[i] & MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ) { + add_char.permissions |= BT_GATT_PERM_READ; + add_char.properties |= BT_GATT_CHRC_READ; + } + if (characteristic_flags[i] & MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY) { + add_char.properties |= BT_GATT_CHRC_NOTIFY; + } + if (characteristic_flags[i] & MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE) { + add_char.properties |= BT_GATT_CHRC_INDICATE; + } + if (characteristic_flags[i] & (MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE | MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_NO_RESPONSE)) { + add_char.permissions |= BT_GATT_PERM_WRITE; + add_char.properties |= (BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP); + } + + add_characteristic(&add_char, &svc_attributes[attr_index], &svc_attributes[attr_index + 1]); + + struct bt_gatt_attr *curr_char = &svc_attributes[attr_index]; + attrs_are_chrs |= (1 << attr_index); + if (characteristic_flags[i] & (MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY | MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE)) { + chr_has_ccc |= (1 << attr_index); + } + attr_index += 1; + attrs_to_ignore |= (1 << attr_index); // ignore the value handle + attr_index += 1; + + if (num_descriptors[i] > 0) { + for (size_t j = 0; j < num_descriptors[i]; ++j) { + + struct add_descriptor add_desc; + add_desc.uuid = create_zephyr_uuid(descriptor_uuids[descriptor_index]); + add_desc.permissions = 0; + if (descriptor_flags[descriptor_index] & MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ) { + add_desc.permissions |= BT_GATT_PERM_READ; + } + if (descriptor_flags[descriptor_index] & (MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE | MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_NO_RESPONSE)) { + add_desc.permissions |= BT_GATT_PERM_WRITE; + } + + add_descriptor(curr_char, &add_desc, &svc_attributes[attr_index]); + attr_index += 1; + + descriptor_index++; + } + } + + // to support indications and notifications we must add the CCC descriptor manually + if (characteristic_flags[i] & (MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY | MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE)) { + struct add_descriptor add_desc; + mp_obj_bluetooth_uuid_t ccc_uuid; + ccc_uuid.base.type = &mp_type_bluetooth_uuid; + ccc_uuid.data[0] = BT_UUID_GATT_CCC_VAL & 0xff; + ccc_uuid.data[1] = (BT_UUID_GATT_CCC_VAL >> 8) & 0xff; + ccc_uuid.type = MP_BLUETOOTH_UUID_TYPE_16; + add_desc.uuid = create_zephyr_uuid(&ccc_uuid); + add_desc.permissions = BT_GATT_PERM_READ | BT_GATT_PERM_WRITE; + + attrs_to_ignore |= (1 << attr_index); + + add_descriptor(curr_char, &add_desc, &svc_attributes[attr_index]); + attr_index += 1; + } + } + + struct bt_gatt_service *service = m_new(struct bt_gatt_service, 1); + mp_obj_list_append(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list, MP_OBJ_FROM_PTR(service)); + service->attrs = svc_attributes; + service->attr_count = attr_index; + // invalidate the last attribute uuid pointer so that we new this is the end of attributes for this service + svc_attributes[attr_index].uuid = NULL; + + // Note: advertising must be stopped for gatts registration to work + + int err = bt_gatt_service_register(service); + if (err) { + return bt_err_to_errno(err); + } + + // now that the service has been registered, we can assign the handles for the characteristics and the descriptors + // we are not interested in the handle of the service itself, so we start the loop from index 1 + for (int i = 1; i < total_attributes; i++) { + // store all the relevant handles (characteristics and descriptors defined in Python) + if (!((uint64_t)(attrs_to_ignore >> i) & (uint64_t)0x01)) { + if (svc_attributes[i].user_data == NULL) { + mp_bluetooth_gatts_db_create_entry(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, svc_attributes[i].handle, MP_BLUETOOTH_DEFAULT_ATTR_LEN); + mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, svc_attributes[i].handle); + svc_attributes[i].user_data = entry->data; + } else if (((uint64_t)(attrs_are_chrs >> i) & (uint64_t)0x01)) { + if (svc_attributes[i + 1].user_data == NULL) { + mp_bluetooth_gatts_db_create_entry(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, svc_attributes[i].handle, MP_BLUETOOTH_DEFAULT_ATTR_LEN); + mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, svc_attributes[i].handle); + svc_attributes[i + 1].user_data = entry->data; + + if (((uint64_t)(chr_has_ccc >> i) & (uint64_t)0x01)) { + // create another database entry for the ccc of this characteristic + mp_bluetooth_gatts_db_create_entry(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, svc_attributes[i].handle + 2, 1); + } + } + } + handles[handle_index++] = svc_attributes[i].handle; + } + } + + MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[MP_STATE_PORT(bluetooth_zephyr_root_pointers)->n_services++] = service; + + return 0; + + #else return MP_EOPNOTSUPP; + #endif } int mp_bluetooth_gap_disconnect(uint16_t conn_handle) { if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } - return MP_EOPNOTSUPP; + mp_bt_zephyr_conn_t *connection = mp_bt_zephyr_find_connection(conn_handle); + if (connection) { + return bt_conn_disconnect(connection->conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + } + return MP_ENOENT; } int mp_bluetooth_gatts_read(uint16_t value_handle, const uint8_t **value, size_t *value_len) { @@ -312,24 +641,155 @@ int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } - if (send_update) { - return MP_EOPNOTSUPP; + + int err = mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, value_handle, value, value_len); + + if ((err == 0) && send_update) { + struct bt_gatt_attr *attr_val = mp_bt_zephyr_find_attr_by_handle(value_handle + 1); + mp_bluetooth_gatts_db_entry_t *ccc_entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, value_handle + 2); + + if (ccc_entry && (ccc_entry->data[0] == BT_GATT_CCC_NOTIFY)) { + err = bt_gatt_notify(NULL, attr_val, value, value_len); + } else if (ccc_entry && (ccc_entry->data[0] == BT_GATT_CCC_INDICATE)) { + struct bt_gatt_indicate_params params = { + .uuid = NULL, + .attr = attr_val, + .func = mp_bt_zephyr_gatt_indicate_done, + .destroy = NULL, + .data = value, + .len = value_len + }; + err = bt_gatt_indicate(NULL, ¶ms); + } + } + return err; +} + +static void mp_bt_zephyr_gatt_indicate_done(struct bt_conn *conn, struct bt_gatt_indicate_params *params, uint8_t err) { + struct bt_conn_info info; + bt_conn_get_info(conn, &info); + uint16_t chr_handle = params->attr->handle - 1; + mp_bluetooth_gatts_on_indicate_complete(info.id, chr_handle, err); +} + +static ssize_t mp_bt_zephyr_gatts_attr_read(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { + // we receive the value handle, but to look up in the gatts db we need the characteristic handle, and that is is the value handle minus 1 + uint16_t _handle = attr->handle - 1; + + DEBUG_printf("BLE attr read for handle %d\n", attr->handle); + + mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, _handle); + if (!entry) { + // it could be a descriptor instead + _handle = attr->handle; + entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, _handle); + if (!entry) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_HANDLE); + } + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, entry->data, entry->data_len); +} + +static ssize_t mp_bt_zephyr_gatts_attr_write(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { + struct bt_conn_info info; + bt_conn_get_info(conn, &info); + + DEBUG_printf("BLE attr write for handle %d\n", attr->handle); + + // the characteristic handle is the value handle minus 1 + uint16_t _handle = attr->handle - 1; + + mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, _handle); + if (!entry) { + // it could be a descriptor instead + _handle = attr->handle; + entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, _handle); + if (!entry) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_HANDLE); + } + } + + // Don't write anything if prepare flag is set + if (flags & BT_GATT_WRITE_FLAG_PREPARE) { + return 0; } - return mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, value_handle, value, value_len); + + if (offset > entry->data_alloc) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if ((offset + len) > entry->data_alloc) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + if (entry->append) { + offset = entry->data_len; + } + + // copy the data into the buffer in the gatts database + memcpy(&entry->data[offset], buf, len); + entry->data_len = offset + len; + + mp_bluetooth_gatts_on_write(info.id, _handle); + + return len; +} + +static struct bt_gatt_attr *mp_bt_zephyr_find_attr_by_handle(uint16_t value_handle) { + for (int i = 0; i < MP_STATE_PORT(bluetooth_zephyr_root_pointers)->n_services; i++) { + int j = 0; + while (MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i]->attrs[j].uuid != NULL) { + if (MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i]->attrs[j].handle == value_handle) { + return &MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i]->attrs[j]; + } + j++; + } + } + return NULL; } int mp_bluetooth_gatts_notify_indicate(uint16_t conn_handle, uint16_t value_handle, int gatts_op, const uint8_t *value, size_t value_len) { if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } - return MP_EOPNOTSUPP; + + int err = MP_ENOENT; + mp_bt_zephyr_conn_t *connection = mp_bt_zephyr_find_connection(conn_handle); + + if (connection) { + struct bt_gatt_attr *attr_val = mp_bt_zephyr_find_attr_by_handle(value_handle + 1); + + if (attr_val) { + switch (gatts_op) { + case MP_BLUETOOTH_GATTS_OP_NOTIFY: { + err = bt_gatt_notify(connection->conn, attr_val, value, value_len); + break; + } + case MP_BLUETOOTH_GATTS_OP_INDICATE: { + struct bt_gatt_indicate_params params = { + .uuid = NULL, + .attr = attr_val, + .func = mp_bt_zephyr_gatt_indicate_done, + .destroy = NULL, + .data = value, + .len = value_len + }; + err = bt_gatt_indicate(connection->conn, ¶ms); + break; + } + } + } + } + + return err; } int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append) { if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } - return MP_EOPNOTSUPP; + return mp_bluetooth_gatts_db_resize(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, value_handle, len, append); } int mp_bluetooth_get_preferred_mtu(void) { @@ -404,6 +864,133 @@ int mp_bluetooth_gap_peripheral_connect_cancel(void) { #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +// Note: modbluetooth UUIDs store their data in LE. +static struct bt_uuid *create_zephyr_uuid(const mp_obj_bluetooth_uuid_t *uuid) { + struct bt_uuid *result = (struct bt_uuid *)m_new(union uuid_u, 1); + mp_obj_list_append(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list, MP_OBJ_FROM_PTR(result)); + if (uuid->type == MP_BLUETOOTH_UUID_TYPE_16) { + bt_uuid_create(result, uuid->data, 2); + } else if (uuid->type == MP_BLUETOOTH_UUID_TYPE_32) { + bt_uuid_create(result, uuid->data, 4); + } else { // MP_BLUETOOTH_UUID_TYPE_128 + bt_uuid_create(result, uuid->data, 16); + } + return result; +} + +static void gatt_db_add(const struct bt_gatt_attr *pattern, struct bt_gatt_attr *attr, size_t user_data_len) { + const union uuid_u *u = CONTAINER_OF(pattern->uuid, union uuid_u, uuid); + size_t uuid_size = sizeof(u->u16); + + if (u->uuid.type == BT_UUID_TYPE_32) { + uuid_size = sizeof(u->u32); + } else if (u->uuid.type == BT_UUID_TYPE_128) { + uuid_size = sizeof(u->u128); + } + + memcpy(attr, pattern, sizeof(*attr)); + + // Store the UUID. + attr->uuid = (const struct bt_uuid *)m_new(union uuid_u, 1); + mp_obj_list_append(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list, MP_OBJ_FROM_PTR(attr->uuid)); + memcpy((void *)attr->uuid, &u->uuid, uuid_size); + + // Copy user_data to the buffer. + if (user_data_len) { + attr->user_data = m_new(uint8_t, user_data_len); + mp_obj_list_append(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->objs_list, MP_OBJ_FROM_PTR(attr->user_data)); + memcpy(attr->user_data, pattern->user_data, user_data_len); + } +} + +static void add_service(const struct bt_uuid *u, struct bt_gatt_attr *attr) { + union uuid_u *uuid = (union uuid_u *)u; + + size_t uuid_size = sizeof(uuid->u16); + + if (uuid->uuid.type == BT_UUID_TYPE_32) { + uuid_size = sizeof(uuid->u32); + } else if (uuid->uuid.type == BT_UUID_TYPE_128) { + uuid_size = sizeof(uuid->u128); + } + + gatt_db_add(&(struct bt_gatt_attr)BT_GATT_PRIMARY_SERVICE(&uuid->uuid), attr, uuid_size); +} + +static void add_characteristic(struct add_characteristic *ch, struct bt_gatt_attr *attr_chrc, struct bt_gatt_attr *attr_value) { + struct bt_gatt_chrc *chrc_data; + + // Add Characteristic Declaration + gatt_db_add(&(struct bt_gatt_attr) + BT_GATT_ATTRIBUTE(BT_UUID_GATT_CHRC, + BT_GATT_PERM_READ, + bt_gatt_attr_read_chrc, NULL, + (&(struct bt_gatt_chrc) {})), attr_chrc, sizeof(*chrc_data)); + + // Allow prepare writes + ch->permissions |= BT_GATT_PERM_PREPARE_WRITE; + + // Add Characteristic Value + gatt_db_add(&(struct bt_gatt_attr) + BT_GATT_ATTRIBUTE(ch->uuid, + ch->permissions & GATT_PERM_MASK, + mp_bt_zephyr_gatts_attr_read, mp_bt_zephyr_gatts_attr_write, NULL), attr_value, 0); + + chrc_data = attr_chrc->user_data; + chrc_data->properties = ch->properties; + chrc_data->uuid = attr_value->uuid; +} + +static void ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { + mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, attr->handle); + entry->data[0] = value; +} + +static struct bt_gatt_attr ccc_definition = BT_GATT_CCC(ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE); + +static void add_ccc(struct bt_gatt_attr *attr, struct bt_gatt_attr *attr_desc) { + struct bt_gatt_chrc *chrc = attr->user_data; + + // Check characteristic properties + if (!(chrc->properties & (BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE))) { + mp_raise_OSError(MP_EINVAL); + } + + // Add CCC descriptor to GATT database + gatt_db_add(&ccc_definition, attr_desc, 0); +} + +static void add_cep(const struct bt_gatt_attr *attr_chrc, struct bt_gatt_attr *attr_desc) { + struct bt_gatt_chrc *chrc = attr_chrc->user_data; + struct bt_gatt_cep cep_value; + + // Extended Properties bit shall be set + if (!(chrc->properties & BT_GATT_CHRC_EXT_PROP)) { + mp_raise_OSError(MP_EINVAL); + } + + cep_value.properties = 0x0000; + + // Add CEP descriptor to GATT database + gatt_db_add(&(struct bt_gatt_attr)BT_GATT_CEP(&cep_value), attr_desc, sizeof(cep_value)); +} + +static void add_descriptor(struct bt_gatt_attr *chrc, struct add_descriptor *d, struct bt_gatt_attr *attr_desc) { + if (!bt_uuid_cmp(d->uuid, BT_UUID_GATT_CEP)) { + add_cep(chrc, attr_desc); + } else if (!bt_uuid_cmp(d->uuid, BT_UUID_GATT_CCC)) { + add_ccc(chrc, attr_desc); + } else { + // Allow prepare writes + d->permissions |= BT_GATT_PERM_PREPARE_WRITE; + + gatt_db_add(&(struct bt_gatt_attr) + BT_GATT_DESCRIPTOR(d->uuid, + d->permissions & GATT_PERM_MASK, + mp_bt_zephyr_gatts_attr_read, mp_bt_zephyr_gatts_attr_write, NULL), attr_desc, 0); + } +} + MP_REGISTER_ROOT_POINTER(struct _mp_bluetooth_zephyr_root_pointers_t *bluetooth_zephyr_root_pointers); #endif // MICROPY_PY_BLUETOOTH From 45e4deb96daf23e648d1cb52100d12da275cdfe8 Mon Sep 17 00:00:00 2001 From: Anton Blanchard Date: Thu, 31 Oct 2024 15:41:28 +1100 Subject: [PATCH 074/154] zephyr/mpconfigport: Fix mp_int_t and mp_uint_t to work on 64-bit archs. These both need to fit a pointer, so make them `intptr_t` and `uintptr_t`, similar to other ports. Signed-off-by: Anton Blanchard --- ports/zephyr/mpconfigport.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index e015776a4eb64..3a6e93486228a 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -139,8 +139,8 @@ void mp_hal_signal_event(void); #define MICROPY_HW_MCU_NAME "unknown-cpu" #endif -typedef int mp_int_t; // must be pointer size -typedef unsigned mp_uint_t; // must be pointer size +typedef intptr_t mp_int_t; // must be pointer size +typedef uintptr_t mp_uint_t; // must be pointer size typedef long mp_off_t; #define MP_STATE_PORT MP_STATE_VM From b2cda6c6046132738fdf7d354b811f216c91bf52 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 1 Apr 2025 16:38:47 +1100 Subject: [PATCH 075/154] extmod,alif,mimxrt,rp2,stm32: Create common cyw43 driver config header. This is only a surface level refactor, some deeper refactoring would be possible with (for example) the SDIO interface in mimxrt and stm32, or the BTHCI interface which is is similar on supported ports. But sticking to cases where the macros are the same across all ports. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- extmod/cyw43_config_common.h | 110 ++++++++++++++++++++++++++++++++ extmod/mpbthci.h | 4 ++ ports/alif/cyw43_configport.h | 54 +--------------- ports/mimxrt/cyw43_configport.h | 50 +-------------- ports/rp2/cyw43_configport.h | 94 +++++---------------------- ports/stm32/cyw43_configport.h | 67 +------------------ 6 files changed, 135 insertions(+), 244 deletions(-) create mode 100644 extmod/cyw43_config_common.h diff --git a/extmod/cyw43_config_common.h b/extmod/cyw43_config_common.h new file mode 100644 index 0000000000000..091b6d3e650dd --- /dev/null +++ b/extmod/cyw43_config_common.h @@ -0,0 +1,110 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Damien P. George, Angus Gratton + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_EXTMOD_CYW43_CONFIG_COMMON_H +#define MICROPY_INCLUDED_EXTMOD_CYW43_CONFIG_COMMON_H + +// The board-level config will be included here, so it can set some CYW43 values. +#include "py/mpconfig.h" +#include "py/mperrno.h" +#include "py/mphal.h" +#include "py/runtime.h" +#include "extmod/modnetwork.h" +#include "lwip/apps/mdns.h" +#include "pendsv.h" + +// This file is included at the top of port-specific cyw43_configport.h files, +// and holds the MicroPython-specific but non-port-specific parts. +// +// It's included into both MicroPython sources and the lib/cyw43-driver sources. + +#define CYW43_IOCTL_TIMEOUT_US (1000000) +#define CYW43_NETUTILS (1) +#define CYW43_PRINTF(...) mp_printf(MP_PYTHON_PRINTER, __VA_ARGS__) + +#define CYW43_EPERM MP_EPERM // Operation not permitted +#define CYW43_EIO MP_EIO // I/O error +#define CYW43_EINVAL MP_EINVAL // Invalid argument +#define CYW43_ETIMEDOUT MP_ETIMEDOUT // Connection timed out + +#define CYW43_THREAD_ENTER MICROPY_PY_LWIP_ENTER +#define CYW43_THREAD_EXIT MICROPY_PY_LWIP_EXIT +#define CYW43_THREAD_LOCK_CHECK + +#define CYW43_HOST_NAME mod_network_hostname_data + +#define CYW43_ARRAY_SIZE(a) MP_ARRAY_SIZE(a) + +#define CYW43_HAL_PIN_MODE_INPUT MP_HAL_PIN_MODE_INPUT +#define CYW43_HAL_PIN_MODE_OUTPUT MP_HAL_PIN_MODE_OUTPUT +#define CYW43_HAL_PIN_PULL_NONE MP_HAL_PIN_PULL_NONE +#define CYW43_HAL_PIN_PULL_UP MP_HAL_PIN_PULL_UP +#define CYW43_HAL_PIN_PULL_DOWN MP_HAL_PIN_PULL_DOWN + +#define CYW43_HAL_MAC_WLAN0 MP_HAL_MAC_WLAN0 +#define CYW43_HAL_MAC_BDADDR MP_HAL_MAC_BDADDR + +#define cyw43_hal_ticks_us mp_hal_ticks_us +#define cyw43_hal_ticks_ms mp_hal_ticks_ms + +#define cyw43_hal_pin_obj_t mp_hal_pin_obj_t +#define cyw43_hal_pin_config mp_hal_pin_config +#define cyw43_hal_pin_read mp_hal_pin_read +#define cyw43_hal_pin_low mp_hal_pin_low +#define cyw43_hal_pin_high mp_hal_pin_high + +#define cyw43_hal_get_mac mp_hal_get_mac +#define cyw43_hal_get_mac_ascii mp_hal_get_mac_ascii +#define cyw43_hal_generate_laa_mac mp_hal_generate_laa_mac + +#define cyw43_schedule_internal_poll_dispatch(func) pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, func) + +// Note: this function is only called if CYW43_POST_POLL_HOOK is defined in cyw43_configport.h +void cyw43_post_poll_hook(void); + +#ifdef MICROPY_EVENT_POLL_HOOK +// Older style hook macros on some ports +#define CYW43_EVENT_POLL_HOOK MICROPY_EVENT_POLL_HOOK +#else +// Newer style hooks on other ports +#define CYW43_EVENT_POLL_HOOK mp_event_handle_nowait() +#endif + +static inline void cyw43_delay_us(uint32_t us) { + uint32_t start = mp_hal_ticks_us(); + while (mp_hal_ticks_us() - start < us) { + } +} + +static inline void cyw43_delay_ms(uint32_t ms) { + // PendSV may be disabled via CYW43_THREAD_ENTER, so this delay is a busy loop. + uint32_t us = ms * 1000; + uint32_t start = mp_hal_ticks_us(); + while (mp_hal_ticks_us() - start < us) { + CYW43_EVENT_POLL_HOOK; + } +} + +#endif // MICROPY_INCLUDED_EXTMOD_CYW43_CONFIG_COMMON_H diff --git a/extmod/mpbthci.h b/extmod/mpbthci.h index c10f99c3dfd7c..3ff8294c4c6c5 100644 --- a/extmod/mpbthci.h +++ b/extmod/mpbthci.h @@ -27,6 +27,10 @@ #ifndef MICROPY_INCLUDED_EXTMOD_MPBTHCI_H #define MICROPY_INCLUDED_EXTMOD_MPBTHCI_H +#include +#include +#include + #define MICROPY_PY_BLUETOOTH_HCI_READ_MODE_BYTE (0) #define MICROPY_PY_BLUETOOTH_HCI_READ_MODE_PACKET (1) diff --git a/ports/alif/cyw43_configport.h b/ports/alif/cyw43_configport.h index 4b5cce67132e9..bfd4364ab9076 100644 --- a/ports/alif/cyw43_configport.h +++ b/ports/alif/cyw43_configport.h @@ -27,13 +27,8 @@ #define MICROPY_INCLUDED_ALIF_CYW43_CONFIGPORT_H // The board-level config will be included here, so it can set some CYW43 values. -#include "py/mpconfig.h" -#include "py/mperrno.h" -#include "py/mphal.h" -#include "py/runtime.h" -#include "extmod/modnetwork.h" #include "extmod/mpbthci.h" -#include "pendsv.h" +#include "extmod/cyw43_config_common.h" #ifndef static_assert #define static_assert(expr, msg) typedef int static_assert_##__LINE__[(expr) ? 1 : -1] @@ -53,51 +48,15 @@ #define CYW43_WARN(...) mp_printf(MP_PYTHON_PRINTER, __VA_ARGS__) #endif -#define CYW43_IOCTL_TIMEOUT_US (1000000) -#define CYW43_SLEEP_MAX (50) -#define CYW43_NETUTILS (1) #define CYW43_CLEAR_SDIO_INT (1) -#define CYW43_EPERM MP_EPERM // Operation not permitted -#define CYW43_EIO MP_EIO // I/O error -#define CYW43_EINVAL MP_EINVAL // Invalid argument -#define CYW43_ETIMEDOUT MP_ETIMEDOUT // Connection timed out - -#define CYW43_THREAD_ENTER MICROPY_PY_LWIP_ENTER -#define CYW43_THREAD_EXIT MICROPY_PY_LWIP_EXIT -#define CYW43_THREAD_LOCK_CHECK - -#define CYW43_HOST_NAME mod_network_hostname_data - #define CYW43_SDPCM_SEND_COMMON_WAIT __WFE() #define CYW43_DO_IOCTL_WAIT // __WFE() -#define CYW43_EVENT_POLL_HOOK mp_event_handle_nowait() - -#define CYW43_ARRAY_SIZE(a) MP_ARRAY_SIZE(a) - -#define CYW43_HAL_PIN_MODE_INPUT MP_HAL_PIN_MODE_INPUT -#define CYW43_HAL_PIN_MODE_OUTPUT MP_HAL_PIN_MODE_OUTPUT -#define CYW43_HAL_PIN_PULL_NONE 0 - -#define CYW43_HAL_MAC_WLAN0 MP_HAL_MAC_WLAN0 -#define CYW43_HAL_MAC_BDADDR MP_HAL_MAC_BDADDR - -#define cyw43_hal_ticks_us mp_hal_ticks_us -#define cyw43_hal_ticks_ms mp_hal_ticks_ms - -#define cyw43_hal_pin_obj_t mp_hal_pin_obj_t -#define cyw43_hal_pin_read mp_hal_pin_read -#define cyw43_hal_pin_low mp_hal_pin_low -#define cyw43_hal_pin_high mp_hal_pin_high #define cyw43_hal_uart_set_baudrate mp_bluetooth_hci_uart_set_baudrate #define cyw43_hal_uart_write mp_bluetooth_hci_uart_write #define cyw43_hal_uart_readchar mp_bluetooth_hci_uart_readchar -#define cyw43_hal_get_mac mp_hal_get_mac -#define cyw43_hal_get_mac_ascii mp_hal_get_mac_ascii -#define cyw43_hal_generate_laa_mac mp_hal_generate_laa_mac - #define CYW43_PIN_WL_REG_ON pin_WL_REG_ON #define CYW43_PIN_WL_IRQ pin_WL_IRQ @@ -110,15 +69,7 @@ void cyw43_post_poll_hook(void); -static inline void cyw43_delay_us(uint32_t us) { - uint32_t start = mp_hal_ticks_us(); - while (mp_hal_ticks_us() - start < us) { - } -} - -static inline void cyw43_delay_ms(uint32_t ms) { - mp_hal_delay_ms(ms); -} +#undef cyw43_hal_pin_config // mp_hal_pin_config on alif port is not API compatible static inline void cyw43_hal_pin_config(mp_hal_pin_obj_t pin, uint32_t mode, uint32_t pull, uint32_t alt) { if (mode == MP_HAL_PIN_MODE_INPUT) { @@ -127,7 +78,6 @@ static inline void cyw43_hal_pin_config(mp_hal_pin_obj_t pin, uint32_t mode, uin mp_hal_pin_output(pin); } } - static inline void cyw43_hal_pin_config_irq_falling(mp_hal_pin_obj_t pin, bool enable) { mp_hal_pin_config_irq_falling(pin, enable); } diff --git a/ports/mimxrt/cyw43_configport.h b/ports/mimxrt/cyw43_configport.h index cf2f5d4b9fb55..b1dd6fbe6ae9f 100644 --- a/ports/mimxrt/cyw43_configport.h +++ b/ports/mimxrt/cyw43_configport.h @@ -28,12 +28,8 @@ #define MICROPY_INCLUDED_MIMXRT_CYW43_CONFIGPORT_H // The board-level config will be included here, so it can set some CYW43 values. -#include "py/mpconfig.h" -#include "py/mperrno.h" -#include "py/mphal.h" -#include "extmod/modnetwork.h" #include "extmod/mpbthci.h" -#include "pendsv.h" +#include "extmod/cyw43_config_common.h" #include "sdio.h" #define CYW43_USE_SPI (0) @@ -61,55 +57,15 @@ #define CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE MICROPY_HW_BLE_UART_BAUDRATE_DOWNLOAD_FIRMWARE #endif -#define CYW43_IOCTL_TIMEOUT_US (1000000) -#define CYW43_SLEEP_MAX (50) -#define CYW43_NETUTILS (1) #define CYW43_CLEAR_SDIO_INT (1) -#define CYW43_EPERM MP_EPERM // Operation not permitted -#define CYW43_EIO MP_EIO // I/O error -#define CYW43_EINVAL MP_EINVAL // Invalid argument -#define CYW43_ETIMEDOUT MP_ETIMEDOUT // Connection timed out - -#define CYW43_THREAD_ENTER MICROPY_PY_LWIP_ENTER -#define CYW43_THREAD_EXIT MICROPY_PY_LWIP_EXIT -#define CYW43_THREAD_LOCK_CHECK - -#define CYW43_HOST_NAME mod_network_hostname_data - #define CYW43_SDPCM_SEND_COMMON_WAIT __WFI(); #define CYW43_DO_IOCTL_WAIT __WFI(); -#define CYW43_ARRAY_SIZE(a) MP_ARRAY_SIZE(a) - -#define CYW43_HAL_PIN_MODE_INPUT MP_HAL_PIN_MODE_INPUT -#define CYW43_HAL_PIN_MODE_OUTPUT MP_HAL_PIN_MODE_OUTPUT -#define CYW43_HAL_PIN_PULL_NONE MP_HAL_PIN_PULL_NONE -#define CYW43_HAL_PIN_PULL_UP MP_HAL_PIN_PULL_UP -#define CYW43_HAL_PIN_PULL_DOWN MP_HAL_PIN_PULL_DOWN - -#define CYW43_HAL_MAC_WLAN0 MP_HAL_MAC_WLAN0 -#define CYW43_HAL_MAC_BDADDR MP_HAL_MAC_BDADDR - -#define cyw43_hal_ticks_us mp_hal_ticks_us -#define cyw43_hal_ticks_ms mp_hal_ticks_ms - -#define cyw43_hal_pin_obj_t mp_hal_pin_obj_t -#define cyw43_hal_pin_read mp_hal_pin_read -#define cyw43_hal_pin_low mp_hal_pin_low -#define cyw43_hal_pin_high mp_hal_pin_high - -#define cyw43_hal_get_mac mp_hal_get_mac -#define cyw43_hal_get_mac_ascii mp_hal_get_mac_ascii -#define cyw43_hal_generate_laa_mac mp_hal_generate_laa_mac - #define cyw43_hal_uart_set_baudrate mp_bluetooth_hci_uart_set_baudrate #define cyw43_hal_uart_write mp_bluetooth_hci_uart_write #define cyw43_hal_uart_readchar mp_bluetooth_hci_uart_readchar -#define cyw43_delay_us mp_hal_delay_us -#define cyw43_delay_ms mp_hal_delay_ms - #define cyw43_bluetooth_controller_init mp_bluetooth_hci_controller_init #define cyw43_bluetooth_controller_deinit mp_bluetooth_hci_controller_deinit #define cyw43_bluetooth_controller_woken mp_bluetooth_hci_controller_woken @@ -135,7 +91,7 @@ #define CYW43_PIN_RFSW_VDD pin_WL_RFSW_VDD #endif -#define cyw43_schedule_internal_poll_dispatch(func) pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, func) +#undef cyw43_hal_pin_config // mp_hal_pin_config not yet implemented on this port static inline void cyw43_hal_pin_config(cyw43_hal_pin_obj_t pin, uint8_t mode, uint8_t pull, uint8_t alt) { machine_pin_set_mode(pin, mode); @@ -175,6 +131,4 @@ static inline int cyw43_sdio_transfer_cmd53(bool write, uint32_t block_size, uin return sdio_transfer_cmd53(write, block_size, arg, len, buf); } -#define CYW43_EVENT_POLL_HOOK MICROPY_EVENT_POLL_HOOK - #endif // MICROPY_INCLUDED_MIMXRT_CYW43_CONFIGPORT_H diff --git a/ports/rp2/cyw43_configport.h b/ports/rp2/cyw43_configport.h index 5523305742325..ef35cfafc24c2 100644 --- a/ports/rp2/cyw43_configport.h +++ b/ports/rp2/cyw43_configport.h @@ -26,34 +26,27 @@ #ifndef MICROPY_INCLUDED_RP2_CYW43_CONFIGPORT_H #define MICROPY_INCLUDED_RP2_CYW43_CONFIGPORT_H -// The board-level config will be included here, so it can set some CYW43 values. -#include -#include "py/mpconfig.h" -#include "py/mperrno.h" -#include "py/mphal.h" -#include "py/runtime.h" -#include "extmod/modnetwork.h" -#include "lwip/apps/mdns.h" -#include "pendsv.h" +#include "extmod/cyw43_config_common.h" #define CYW43_INCLUDE_LEGACY_F1_OVERFLOW_WORKAROUND_VARIABLES (1) #define CYW43_WIFI_NVRAM_INCLUDE_FILE "wifi_nvram_43439.h" -#define CYW43_IOCTL_TIMEOUT_US (1000000) -#define CYW43_SLEEP_MAX (10) -#define CYW43_NETUTILS (1) +#define CYW43_SLEEP_MAX (10) // Unclear why rp2 port overrides the default here #define CYW43_USE_OTP_MAC (1) -#define CYW43_PRINTF(...) mp_printf(MP_PYTHON_PRINTER, __VA_ARGS__) -#define CYW43_EPERM MP_EPERM // Operation not permitted -#define CYW43_EIO MP_EIO // I/O error -#define CYW43_EINVAL MP_EINVAL // Invalid argument -#define CYW43_ETIMEDOUT MP_ETIMEDOUT // Connection timed out +static inline bool cyw43_poll_is_pending(void) { + return pendsv_is_pending(PENDSV_DISPATCH_CYW43); +} -#define CYW43_THREAD_ENTER MICROPY_PY_LWIP_ENTER -#define CYW43_THREAD_EXIT MICROPY_PY_LWIP_EXIT -#define CYW43_THREAD_LOCK_CHECK +static inline void cyw43_yield(void) { + if (!cyw43_poll_is_pending()) { + best_effort_wfe_or_timeout(make_timeout_time_ms(1)); + } +} -#define CYW43_HOST_NAME mod_network_hostname_data +#define CYW43_POST_POLL_HOOK cyw43_post_poll_hook(); + +// set in SDK board header +#define CYW43_NUM_GPIOS CYW43_WL_GPIO_COUNT #if CYW43_PIN_WL_DYNAMIC @@ -100,36 +93,6 @@ uint cyw43_get_pin_wl(cyw43_pin_index_t pin_id); cyw43_yield(); \ } \ -#define CYW43_ARRAY_SIZE(a) MP_ARRAY_SIZE(a) - -#define CYW43_HAL_PIN_MODE_INPUT MP_HAL_PIN_MODE_INPUT -#define CYW43_HAL_PIN_MODE_OUTPUT MP_HAL_PIN_MODE_OUTPUT -#define CYW43_HAL_PIN_PULL_NONE MP_HAL_PIN_PULL_NONE -#define CYW43_HAL_PIN_PULL_UP MP_HAL_PIN_PULL_UP -#define CYW43_HAL_PIN_PULL_DOWN MP_HAL_PIN_PULL_DOWN - -#define CYW43_HAL_MAC_WLAN0 MP_HAL_MAC_WLAN0 - -// set in SDK board header -#define CYW43_NUM_GPIOS CYW43_WL_GPIO_COUNT - -#define CYW43_POST_POLL_HOOK cyw43_post_poll_hook(); - -#define cyw43_hal_ticks_us mp_hal_ticks_us -#define cyw43_hal_ticks_ms mp_hal_ticks_ms - -#define cyw43_hal_pin_obj_t mp_hal_pin_obj_t -#define cyw43_hal_pin_config mp_hal_pin_config -#define cyw43_hal_pin_read mp_hal_pin_read -#define cyw43_hal_pin_low mp_hal_pin_low -#define cyw43_hal_pin_high mp_hal_pin_high - -#define cyw43_hal_get_mac mp_hal_get_mac -#define cyw43_hal_get_mac_ascii mp_hal_get_mac_ascii -#define cyw43_hal_generate_laa_mac mp_hal_generate_laa_mac - -#define cyw43_schedule_internal_poll_dispatch(func) pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, func) - // Bluetooth requires dynamic memory allocation to load its firmware (the allocation // call is made from pico-sdk). This allocation is always done at thread-level, not // from an IRQ, so is safe to delegate to the MicroPython GC heap. @@ -140,34 +103,6 @@ uint cyw43_get_pin_wl(cyw43_pin_index_t pin_id); #define cyw43_free m_tracked_free #endif -void cyw43_post_poll_hook(void); -static inline bool cyw43_poll_is_pending(void) { - return pendsv_is_pending(PENDSV_DISPATCH_CYW43); -} - -static inline void cyw43_yield(void) { - if (!cyw43_poll_is_pending()) { - best_effort_wfe_or_timeout(make_timeout_time_ms(1)); - } -} - -static inline void cyw43_delay_us(uint32_t us) { - uint32_t start = mp_hal_ticks_us(); - while (mp_hal_ticks_us() - start < us) { - } -} - -static inline void cyw43_delay_ms(uint32_t ms) { - // PendSV may be disabled via CYW43_THREAD_ENTER, so this delay is a busy loop. - uint32_t us = ms * 1000; - uint32_t start = mp_hal_ticks_us(); - while (mp_hal_ticks_us() - start < us) { - mp_event_handle_nowait(); - } -} - -#define CYW43_EVENT_POLL_HOOK mp_event_handle_nowait() - #if LWIP_MDNS_RESPONDER == 1 // Hook for any additional TCP/IP initialization than needs to be done. @@ -183,4 +118,5 @@ static inline void cyw43_delay_ms(uint32_t ms) { #endif #endif + #endif // MICROPY_INCLUDED_RP2_CYW43_CONFIGPORT_H diff --git a/ports/stm32/cyw43_configport.h b/ports/stm32/cyw43_configport.h index c26c55476167c..6528dbc625e85 100644 --- a/ports/stm32/cyw43_configport.h +++ b/ports/stm32/cyw43_configport.h @@ -27,14 +27,9 @@ #ifndef MICROPY_INCLUDED_STM32_CYW43_CONFIGPORT_H #define MICROPY_INCLUDED_STM32_CYW43_CONFIGPORT_H -// The board-level config will be included here, so it can set some CYW43 values. -#include "py/mpconfig.h" -#include "py/mperrno.h" -#include "py/mphal.h" -#include "extmod/modnetwork.h" -#include "extmod/mpbthci.h" #include "extint.h" -#include "pendsv.h" +#include "extmod/mpbthci.h" +#include "extmod/cyw43_config_common.h" #include "sdio.h" #define CYW43_USE_SPI (0) @@ -62,50 +57,12 @@ #define CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE MICROPY_HW_BLE_UART_BAUDRATE_DOWNLOAD_FIRMWARE #endif -#define CYW43_IOCTL_TIMEOUT_US (1000000) -#define CYW43_SLEEP_MAX (50) -#define CYW43_NETUTILS (1) #define CYW43_CLEAR_SDIO_INT (1) -#define CYW43_EPERM MP_EPERM // Operation not permitted -#define CYW43_EIO MP_EIO // I/O error -#define CYW43_EINVAL MP_EINVAL // Invalid argument -#define CYW43_ETIMEDOUT MP_ETIMEDOUT // Connection timed out - -#define CYW43_THREAD_ENTER MICROPY_PY_LWIP_ENTER -#define CYW43_THREAD_EXIT MICROPY_PY_LWIP_EXIT -#define CYW43_THREAD_LOCK_CHECK - -#define CYW43_HOST_NAME mod_network_hostname_data - #define CYW43_SDPCM_SEND_COMMON_WAIT __WFI(); #define CYW43_DO_IOCTL_WAIT __WFI(); #define CYW43_HAL_UART_READCHAR_BLOCKING_WAIT __WFI() -#define CYW43_ARRAY_SIZE(a) MP_ARRAY_SIZE(a) - -#define CYW43_HAL_PIN_MODE_INPUT MP_HAL_PIN_MODE_INPUT -#define CYW43_HAL_PIN_MODE_OUTPUT MP_HAL_PIN_MODE_OUTPUT -#define CYW43_HAL_PIN_PULL_NONE MP_HAL_PIN_PULL_NONE -#define CYW43_HAL_PIN_PULL_UP MP_HAL_PIN_PULL_UP -#define CYW43_HAL_PIN_PULL_DOWN MP_HAL_PIN_PULL_DOWN - -#define CYW43_HAL_MAC_WLAN0 MP_HAL_MAC_WLAN0 -#define CYW43_HAL_MAC_BDADDR MP_HAL_MAC_BDADDR - -#define cyw43_hal_ticks_us mp_hal_ticks_us -#define cyw43_hal_ticks_ms mp_hal_ticks_ms - -#define cyw43_hal_pin_obj_t mp_hal_pin_obj_t -#define cyw43_hal_pin_config mp_hal_pin_config -#define cyw43_hal_pin_read mp_hal_pin_read -#define cyw43_hal_pin_low mp_hal_pin_low -#define cyw43_hal_pin_high mp_hal_pin_high - -#define cyw43_hal_get_mac mp_hal_get_mac -#define cyw43_hal_get_mac_ascii mp_hal_get_mac_ascii -#define cyw43_hal_generate_laa_mac mp_hal_generate_laa_mac - #define cyw43_hal_uart_set_baudrate mp_bluetooth_hci_uart_set_baudrate #define cyw43_hal_uart_write mp_bluetooth_hci_uart_write #define cyw43_hal_uart_readchar mp_bluetooth_hci_uart_readchar @@ -132,24 +89,6 @@ #define CYW43_PIN_RFSW_SELECT pyb_pin_WL_GPIO_1 #endif -#define cyw43_schedule_internal_poll_dispatch(func) pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, func) - -void cyw43_post_poll_hook(void); - -static inline void cyw43_delay_us(uint32_t us) { - uint32_t start = mp_hal_ticks_us(); - while (mp_hal_ticks_us() - start < us) { - } -} - -static inline void cyw43_delay_ms(uint32_t ms) { - uint32_t us = ms * 1000; - uint32_t start = mp_hal_ticks_us(); - while (mp_hal_ticks_us() - start < us) { - MICROPY_EVENT_POLL_HOOK; - } -} - static inline void cyw43_hal_pin_config_irq_falling(cyw43_hal_pin_obj_t pin, int enable) { if (enable) { extint_set(pin, GPIO_MODE_IT_FALLING); @@ -184,6 +123,4 @@ static inline int cyw43_sdio_transfer_cmd53(bool write, uint32_t block_size, uin return sdio_transfer_cmd53(write, block_size, arg, len, buf); } -#define CYW43_EVENT_POLL_HOOK MICROPY_EVENT_POLL_HOOK - #endif // MICROPY_INCLUDED_STM32_CYW43_CONFIGPORT_H From d00eab4a3056ebaf47a1d2fdf8c21d132c465055 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 8 Apr 2025 10:23:11 +1000 Subject: [PATCH 076/154] rp2,extmod/cyw43: Move the LWIP responder fix into common CYW43 config. This means the fix from dd1465e7 will also apply to stm32 and mimxrt ports that use CYW43. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- extmod/cyw43_config_common.h | 16 ++++++++++++++++ ports/rp2/cyw43_configport.h | 16 ---------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/extmod/cyw43_config_common.h b/extmod/cyw43_config_common.h index 091b6d3e650dd..595af37d71318 100644 --- a/extmod/cyw43_config_common.h +++ b/extmod/cyw43_config_common.h @@ -107,4 +107,20 @@ static inline void cyw43_delay_ms(uint32_t ms) { } } +#if LWIP_MDNS_RESPONDER == 1 + +// Hook for any additional TCP/IP initialization than needs to be done. +// Called after the netif specified by `itf` has been set up. +#ifndef CYW43_CB_TCPIP_INIT_EXTRA +#define CYW43_CB_TCPIP_INIT_EXTRA(self, itf) mdns_resp_add_netif(&self->netif[itf], mod_network_hostname_data) +#endif + +// Hook for any additional TCP/IP deinitialization than needs to be done. +// Called before the netif specified by `itf` is removed. +#ifndef CYW43_CB_TCPIP_DEINIT_EXTRA +#define CYW43_CB_TCPIP_DEINIT_EXTRA(self, itf) mdns_resp_remove_netif(&self->netif[itf]) +#endif + +#endif + #endif // MICROPY_INCLUDED_EXTMOD_CYW43_CONFIG_COMMON_H diff --git a/ports/rp2/cyw43_configport.h b/ports/rp2/cyw43_configport.h index ef35cfafc24c2..2da0b1f8a4982 100644 --- a/ports/rp2/cyw43_configport.h +++ b/ports/rp2/cyw43_configport.h @@ -103,20 +103,4 @@ uint cyw43_get_pin_wl(cyw43_pin_index_t pin_id); #define cyw43_free m_tracked_free #endif -#if LWIP_MDNS_RESPONDER == 1 - -// Hook for any additional TCP/IP initialization than needs to be done. -// Called after the netif specified by `itf` has been set up. -#ifndef CYW43_CB_TCPIP_INIT_EXTRA -#define CYW43_CB_TCPIP_INIT_EXTRA(self, itf) mdns_resp_add_netif(&self->netif[itf], mod_network_hostname_data) -#endif - -// Hook for any additional TCP/IP deinitialization than needs to be done. -// Called before the netif specified by `itf` is removed. -#ifndef CYW43_CB_TCPIP_DEINIT_EXTRA -#define CYW43_CB_TCPIP_DEINIT_EXTRA(self, itf) mdns_resp_remove_netif(&self->netif[itf]) -#endif - -#endif - #endif // MICROPY_INCLUDED_RP2_CYW43_CONFIGPORT_H From 928466d74c10c09570e63030419804889f8e0e98 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 28 Apr 2025 12:25:05 +1000 Subject: [PATCH 077/154] rp2/Makefile: Add deploy target that uses picotool load. This is a convenient way to deploy firmware to an RP2xxx-based board. Signed-off-by: Damien George --- ports/rp2/Makefile | 3 +++ ports/rp2/README.md | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ports/rp2/Makefile b/ports/rp2/Makefile index 76b5698d2c8ec..2895faaca6178 100644 --- a/ports/rp2/Makefile +++ b/ports/rp2/Makefile @@ -67,6 +67,9 @@ all: clean: $(RM) -rf $(BUILD) +deploy: all + $(Q)picotool load -x $(BUILD)/firmware.elf + # First ensure that pico-sdk is initialised, then run CMake with the # UPDATE_SUBMODULES flag to update necessary submodules for this board. # diff --git a/ports/rp2/README.md b/ports/rp2/README.md index 911d797fe01a2..41fca97f95ec9 100644 --- a/ports/rp2/README.md +++ b/ports/rp2/README.md @@ -47,8 +47,10 @@ pass the board name to the build; e.g. for Raspberry Pi Pico W: ## Deploying firmware to the device Firmware can be deployed to the device by putting it into bootloader mode -(hold down BOOTSEL while powering on or resetting) and then copying -`firmware.uf2` to the USB mass storage device that appears. +(hold down BOOTSEL while powering on or resetting) and then either copying +`firmware.uf2` to the USB mass storage device that appears, or using +`picotool load -x firmware.elf`. The latter command can be accessed +conveniently via `make deploy`. If MicroPython is already installed then the bootloader can be entered by executing `import machine; machine.bootloader()` at the REPL. From 9c8c219f8af4128822dc525cc7d934533bd4675a Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 28 Apr 2025 12:51:51 +1000 Subject: [PATCH 078/154] rp2/rp2_dma: Fix default value used in pack_ctrl on RP2350. The bit position of CHAIN_TO is not the same as on RP2040. Signed-off-by: Damien George --- ports/rp2/rp2_dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/rp2/rp2_dma.c b/ports/rp2/rp2_dma.c index 78f69e6452758..94c61e226e695 100644 --- a/ports/rp2/rp2_dma.c +++ b/ports/rp2/rp2_dma.c @@ -315,7 +315,7 @@ static mp_obj_t rp2_dma_pack_ctrl(size_t n_pos_args, const mp_obj_t *pos_args, m // Pack keyword settings into a control register value, using either the default for this // DMA channel or the provided defaults rp2_dma_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); - mp_uint_t value = DEFAULT_DMA_CONFIG | ((self->channel & 0xf) << 11); + mp_uint_t value = DEFAULT_DMA_CONFIG | ((self->channel & 0xf) << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB); if (n_pos_args > 1) { mp_raise_TypeError(MP_ERROR_TEXT("pack_ctrl only takes keyword arguments")); From d01a981a9b924ccb8b25b8d13621c3bba3d04bb4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 7 May 2025 13:49:12 +1000 Subject: [PATCH 079/154] py/mpconfig: Enable io.IOBase at core feature level. IOBase is quite an important building block of other parts of the system, such as `mpremote mount` and running .mpy and native tests. This feature costs +244 bytes of firmware size on ARM Thumb2 architectures, which is worth the cost for the extra features it enables. The change here means that `io.IOBase` is now enabled on all nrf boards, (previously it was only nRF52840 and nRF9160) and also B_L072Z_LRWAN1 (there is no change to other ports or boards). Signed-off-by: Damien George --- py/mpconfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/mpconfig.h b/py/mpconfig.h index d06932a77b742..9001b8983bf37 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1469,7 +1469,7 @@ typedef double mp_float_t; // Whether to provide "io.IOBase" class to support user streams #ifndef MICROPY_PY_IO_IOBASE -#define MICROPY_PY_IO_IOBASE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) +#define MICROPY_PY_IO_IOBASE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) #endif // Whether to provide "io.BytesIO" class From 3fa77bdc7d413a410fb85a8943d417bca3b562e7 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 9 May 2025 14:01:48 +1000 Subject: [PATCH 080/154] rp2: Add temporary workaround for GCC 15.1 build failure. This is a workaround for this upstream issue: https://github.com/raspberrypi/pico-sdk/issues/2448 Can be removed after the next pico-sdk update. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/rp2/CMakeLists.txt | 3 ++ ports/rp2/tools_patch/Findpioasm.cmake | 58 ++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 ports/rp2/tools_patch/Findpioasm.cmake diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 53d00c7dfdaa4..7cb0c60aa21f4 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -84,6 +84,9 @@ endif() list(APPEND GIT_SUBMODULES lib/mbedtls) list(APPEND GIT_SUBMODULES lib/tinyusb) +# Workaround for pico-sdk host toolchain issue, see directory for details +list(APPEND CMAKE_MODULE_PATH "${MICROPY_PORT_DIR}/tools_patch") + # Include component cmake fragments include(${MICROPY_DIR}/py/py.cmake) include(${MICROPY_DIR}/extmod/extmod.cmake) diff --git a/ports/rp2/tools_patch/Findpioasm.cmake b/ports/rp2/tools_patch/Findpioasm.cmake new file mode 100644 index 0000000000000..72e6bf6fb61c0 --- /dev/null +++ b/ports/rp2/tools_patch/Findpioasm.cmake @@ -0,0 +1,58 @@ +# Finds (or builds) the pioasm executable +# +# This will define the following imported targets +# +# pioasm +# + +# This is a temporary patched copy of pico-sdk file Findpioasm.cmake to work around +# a host toolchain issue with GCC 15.1: +# https://github.com/raspberrypi/pico-sdk/issues/2448 + +if (NOT TARGET pioasm) + # todo we would like to use pckgconfig to look for it first + # see https://pabloariasal.github.io/2018/02/19/its-time-to-do-cmake-right/ + + include(ExternalProject) + + set(PIOASM_SOURCE_DIR ${PICO_SDK_PATH}/tools/pioasm) + set(PIOASM_BINARY_DIR ${CMAKE_BINARY_DIR}/pioasm) + set(PIOASM_INSTALL_DIR ${CMAKE_BINARY_DIR}/pioasm-install CACHE PATH "Directory where pioasm has been installed" FORCE) + + set(pioasmBuild_TARGET pioasmBuild) + set(pioasm_TARGET pioasm) + + if (NOT TARGET ${pioasmBuild_TARGET}) + pico_message_debug("PIOASM will need to be built") +# message("Adding external project ${pioasmBuild_Target} in ${CMAKE_CURRENT_LIST_DIR}}") + ExternalProject_Add(${pioasmBuild_TARGET} + PREFIX pioasm + SOURCE_DIR ${PIOASM_SOURCE_DIR} + BINARY_DIR ${PIOASM_BINARY_DIR} + INSTALL_DIR ${PIOASM_INSTALL_DIR} + CMAKE_ARGS + "--no-warn-unused-cli" + "-DCMAKE_MAKE_PROGRAM:FILEPATH=${CMAKE_MAKE_PROGRAM}" + "-DPIOASM_FLAT_INSTALL=1" + "-DCMAKE_INSTALL_PREFIX=${PIOASM_INSTALL_DIR}" + "-DCMAKE_RULE_MESSAGES=OFF" # quieten the build + "-DCMAKE_INSTALL_MESSAGE=NEVER" # quieten the install + # Toolchain workaround follows + "-DCMAKE_CXX_FLAGS=-include cstdint" + CMAKE_CACHE_ARGS "-DPIOASM_EXTRA_SOURCE_FILES:STRING=${PIOASM_EXTRA_SOURCE_FILES}" + BUILD_ALWAYS 1 # force dependency checking + EXCLUDE_FROM_ALL TRUE + ) + endif() + + if (CMAKE_HOST_WIN32) + set(pioasm_EXECUTABLE ${PIOASM_INSTALL_DIR}/pioasm/pioasm.exe) + else() + set(pioasm_EXECUTABLE ${PIOASM_INSTALL_DIR}/pioasm/pioasm) + endif() + add_executable(${pioasm_TARGET} IMPORTED GLOBAL) + set_property(TARGET ${pioasm_TARGET} PROPERTY IMPORTED_LOCATION + ${pioasm_EXECUTABLE}) + + add_dependencies(${pioasm_TARGET} ${pioasmBuild_TARGET}) +endif() From 7d5aba0523cac0a23dc63a5bb4b64a8eab845836 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 9 May 2025 13:34:37 +1000 Subject: [PATCH 081/154] extmod/moductypes: Refactor string literal as array initializer. Avoids the new Wunterminated-string-literal when compiled with gcc 15.1. Also split out the duplicate string to a top-level array (probably the duplicate string literal was interned, so unlikely to have any impact.) This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- extmod/moductypes.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/extmod/moductypes.c b/extmod/moductypes.c index bf45797658fa0..54abce79e06fe 100644 --- a/extmod/moductypes.c +++ b/extmod/moductypes.c @@ -277,15 +277,18 @@ static mp_obj_t uctypes_struct_sizeof(size_t n_args, const mp_obj_t *args) { } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(uctypes_struct_sizeof_obj, 1, 2, uctypes_struct_sizeof); +static const char type2char[16] = { + 'B', 'b', 'H', 'h', 'I', 'i', 'Q', 'q', + '-', '-', '-', '-', '-', '-', 'f', 'd' +}; + static inline mp_obj_t get_unaligned(uint val_type, byte *p, int big_endian) { char struct_type = big_endian ? '>' : '<'; - static const char type2char[16] = "BbHhIiQq------fd"; return mp_binary_get_val(struct_type, type2char[val_type], p, &p); } static inline void set_unaligned(uint val_type, byte *p, int big_endian, mp_obj_t val) { char struct_type = big_endian ? '>' : '<'; - static const char type2char[16] = "BbHhIiQq------fd"; mp_binary_set_val(struct_type, type2char[val_type], val, p, &p); } From 9f8600588584cb58fcc5593f18b090622a406565 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 9 May 2025 13:36:05 +1000 Subject: [PATCH 082/154] py/emitinlinethumb: Refactor string literal as array initializer. Avoids the new Wunterminated-string-literal when compiled with gcc 15.1. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- py/emitinlinethumb.c | 108 +++++++++++++++++++++---------------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/py/emitinlinethumb.c b/py/emitinlinethumb.c index 7818bb4f46da8..d6596337ae5a6 100644 --- a/py/emitinlinethumb.c +++ b/py/emitinlinethumb.c @@ -150,27 +150,27 @@ typedef struct _reg_name_t { byte reg; byte name[3]; } reg_name_t; static const reg_name_t reg_name_table[] = { - {0, "r0\0"}, - {1, "r1\0"}, - {2, "r2\0"}, - {3, "r3\0"}, - {4, "r4\0"}, - {5, "r5\0"}, - {6, "r6\0"}, - {7, "r7\0"}, - {8, "r8\0"}, - {9, "r9\0"}, - {10, "r10"}, - {11, "r11"}, - {12, "r12"}, - {13, "r13"}, - {14, "r14"}, - {15, "r15"}, - {10, "sl\0"}, - {11, "fp\0"}, - {13, "sp\0"}, - {14, "lr\0"}, - {15, "pc\0"}, + {0, {'r', '0' }}, + {1, {'r', '1' }}, + {2, {'r', '2' }}, + {3, {'r', '3' }}, + {4, {'r', '4' }}, + {5, {'r', '5' }}, + {6, {'r', '6' }}, + {7, {'r', '7' }}, + {8, {'r', '8' }}, + {9, {'r', '9' }}, + {10, {'r', '1', '0' }}, + {11, {'r', '1', '1' }}, + {12, {'r', '1', '2' }}, + {13, {'r', '1', '3' }}, + {14, {'r', '1', '4' }}, + {15, {'r', '1', '5' }}, + {10, {'s', 'l' }}, + {11, {'f', 'p' }}, + {13, {'s', 'p' }}, + {14, {'l', 'r' }}, + {15, {'p', 'c' }}, }; #define MAX_SPECIAL_REGISTER_NAME_LENGTH 7 @@ -368,20 +368,20 @@ typedef struct _cc_name_t { byte cc; byte name[2]; } cc_name_t; static const cc_name_t cc_name_table[] = { - { ASM_THUMB_CC_EQ, "eq" }, - { ASM_THUMB_CC_NE, "ne" }, - { ASM_THUMB_CC_CS, "cs" }, - { ASM_THUMB_CC_CC, "cc" }, - { ASM_THUMB_CC_MI, "mi" }, - { ASM_THUMB_CC_PL, "pl" }, - { ASM_THUMB_CC_VS, "vs" }, - { ASM_THUMB_CC_VC, "vc" }, - { ASM_THUMB_CC_HI, "hi" }, - { ASM_THUMB_CC_LS, "ls" }, - { ASM_THUMB_CC_GE, "ge" }, - { ASM_THUMB_CC_LT, "lt" }, - { ASM_THUMB_CC_GT, "gt" }, - { ASM_THUMB_CC_LE, "le" }, + { ASM_THUMB_CC_EQ, { 'e', 'q' }}, + { ASM_THUMB_CC_NE, { 'n', 'e' }}, + { ASM_THUMB_CC_CS, { 'c', 's' }}, + { ASM_THUMB_CC_CC, { 'c', 'c' }}, + { ASM_THUMB_CC_MI, { 'm', 'i' }}, + { ASM_THUMB_CC_PL, { 'p', 'l' }}, + { ASM_THUMB_CC_VS, { 'v', 's' }}, + { ASM_THUMB_CC_VC, { 'v', 'c' }}, + { ASM_THUMB_CC_HI, { 'h', 'i' }}, + { ASM_THUMB_CC_LS, { 'l', 's' }}, + { ASM_THUMB_CC_GE, { 'g', 'e' }}, + { ASM_THUMB_CC_LT, { 'l', 't' }}, + { ASM_THUMB_CC_GT, { 'g', 't' }}, + { ASM_THUMB_CC_LE, { 'l', 'e' }}, }; typedef struct _format_4_op_t { byte op; @@ -389,21 +389,21 @@ typedef struct _format_4_op_t { byte op; } format_4_op_t; #define X(x) (((x) >> 4) & 0xff) // only need 1 byte to distinguish these ops static const format_4_op_t format_4_op_table[] = { - { X(ASM_THUMB_FORMAT_4_EOR), "eor" }, - { X(ASM_THUMB_FORMAT_4_LSL), "lsl" }, - { X(ASM_THUMB_FORMAT_4_LSR), "lsr" }, - { X(ASM_THUMB_FORMAT_4_ASR), "asr" }, - { X(ASM_THUMB_FORMAT_4_ADC), "adc" }, - { X(ASM_THUMB_FORMAT_4_SBC), "sbc" }, - { X(ASM_THUMB_FORMAT_4_ROR), "ror" }, - { X(ASM_THUMB_FORMAT_4_TST), "tst" }, - { X(ASM_THUMB_FORMAT_4_NEG), "neg" }, - { X(ASM_THUMB_FORMAT_4_CMP), "cmp" }, - { X(ASM_THUMB_FORMAT_4_CMN), "cmn" }, - { X(ASM_THUMB_FORMAT_4_ORR), "orr" }, - { X(ASM_THUMB_FORMAT_4_MUL), "mul" }, - { X(ASM_THUMB_FORMAT_4_BIC), "bic" }, - { X(ASM_THUMB_FORMAT_4_MVN), "mvn" }, + { X(ASM_THUMB_FORMAT_4_EOR), {'e', 'o', 'r' }}, + { X(ASM_THUMB_FORMAT_4_LSL), {'l', 's', 'l' }}, + { X(ASM_THUMB_FORMAT_4_LSR), {'l', 's', 'r' }}, + { X(ASM_THUMB_FORMAT_4_ASR), {'a', 's', 'r' }}, + { X(ASM_THUMB_FORMAT_4_ADC), {'a', 'd', 'c' }}, + { X(ASM_THUMB_FORMAT_4_SBC), {'s', 'b', 'c' }}, + { X(ASM_THUMB_FORMAT_4_ROR), {'r', 'o', 'r' }}, + { X(ASM_THUMB_FORMAT_4_TST), {'t', 's', 't' }}, + { X(ASM_THUMB_FORMAT_4_NEG), {'n', 'e', 'g' }}, + { X(ASM_THUMB_FORMAT_4_CMP), {'c', 'm', 'p' }}, + { X(ASM_THUMB_FORMAT_4_CMN), {'c', 'm', 'n' }}, + { X(ASM_THUMB_FORMAT_4_ORR), {'o', 'r', 'r' }}, + { X(ASM_THUMB_FORMAT_4_MUL), {'m', 'u', 'l' }}, + { X(ASM_THUMB_FORMAT_4_BIC), {'b', 'i', 'c' }}, + { X(ASM_THUMB_FORMAT_4_MVN), {'m', 'v', 'n' }}, }; #undef X @@ -428,10 +428,10 @@ typedef struct _format_vfp_op_t { char name[3]; } format_vfp_op_t; static const format_vfp_op_t format_vfp_op_table[] = { - { 0x30, "add" }, - { 0x34, "sub" }, - { 0x20, "mul" }, - { 0x80, "div" }, + { 0x30, {'a', 'd', 'd' }}, + { 0x34, {'s', 'u', 'b' }}, + { 0x20, {'m', 'u', 'l' }}, + { 0x80, {'d', 'i', 'v' }}, }; // shorthand alias for whether we allow ARMv7-M instructions From ae6062a45a776e193e8c02ece62d058b64f2b4f5 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 9 May 2025 14:34:09 +1000 Subject: [PATCH 083/154] lib/littlefs: Fix string initializer in lfs1.c. Avoids the new Wunterminated-string-literal when compiled with gcc 15.1. It would be preferable to just disable this warning, but Clang -Wunknown-warning-option kicks in even when disabling warnings so this becomes fiddly to apply. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- lib/littlefs/lfs1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/littlefs/lfs1.c b/lib/littlefs/lfs1.c index 6a3fd670012cc..ec18dc470258c 100644 --- a/lib/littlefs/lfs1.c +++ b/lib/littlefs/lfs1.c @@ -2141,7 +2141,7 @@ int lfs1_format(lfs1_t *lfs1, const struct lfs1_config *cfg) { .d.elen = sizeof(superblock.d) - sizeof(superblock.d.magic) - 4, .d.nlen = sizeof(superblock.d.magic), .d.version = LFS1_DISK_VERSION, - .d.magic = {"littlefs"}, + .d.magic = {'l', 'i', 't', 't', 'l', 'e', 'f', 's'}, .d.block_size = lfs1->cfg->block_size, .d.block_count = lfs1->cfg->block_count, .d.root = {lfs1->root[0], lfs1->root[1]}, From f7c53cd221a4d8b1a7b3098ab71db06d00da51ad Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 1 May 2025 12:39:29 +1000 Subject: [PATCH 084/154] tests/net_inet: Update micropython.org certificate for SSL tests. The Let's Encrypt root certificate has changed so needs updating in these tests. Also use `bytes.fromhex()` instead of `binascii.unhexlify()`, to eliminate the need for the `binascii` module. Both of these features are controlled by `MICROPY_PY_BUILTINS_BYTES_HEX`, so the test will still work on the same targets that it previously did. Signed-off-by: Damien George --- tests/net_inet/mpycert.der | Bin 1290 -> 1289 bytes tests/net_inet/ssl_cert.py | 79 +++++++++++------------ tests/net_inet/test_sslcontext_client.py | 10 ++- 3 files changed, 43 insertions(+), 46 deletions(-) diff --git a/tests/net_inet/mpycert.der b/tests/net_inet/mpycert.der index 0b0eabc9bc8135de25785cb8574ab78e03947aa9..ac22dcf9e8b888a98c10bd12f2108feb696b68c8 100644 GIT binary patch delta 849 zcmV-X1Frmv3W*9IFoFdIFoFW@paTK{0s;_As8W;nnlGYr21}>}jdjqGAhZE6k<}r8 z&sXK<#8eXPv`7BQCs=kyP$-NYK8W81oz=A%DZSmbTEF`C&ZEAs03l0aT3S2-FU42` z_KE+F5ku?2wcr#7mXB{A*XO!{ISF-Tx5BR&TQ?>s=cDuoD{UJ8t6031u0a3k%U8s; zTYq>B7-75@=TyeKNs-NN7}0=*?xtmbHoSD|yiiQ){2}u=7L$~^u+wWe2F(@{ddgCD z$gOs=!kk^0MO<%Td&Vv9JQGre0ohmPI2_-NwI7m^n&(O!%tfejRq(LpqX+E$ryFLT zKS+ylgmGBOI{NQk@>@hJJap%evPZ!OYR#Ir4VL#3fhJK@3hZI__x0lPipZ4$4ULob z0a+Bgyu(MO5xQ~qk&w{ z!(IKopm5c(mM9_%(()+J4CmD5LyROM9l=79(Fw1gOZ3QaPaqbtTHOKmANU_(YZNE2 za*boI?&8i%&o|w>qtwmktx&DKzvJvaVm5G@F{kK_-RffE60Y@})IBT=s?eNyzUn=H zTtse`F=JYN)_zYUpi2dRUT@OW>R&TU6V_L@%HC19U84ztbp6M8p0l~E7TQWHmczD- zVvqTB8KRy8-sx|fun*FpTK0up!UVU-)lM?RzXV;pDfah)j|^QrQ^`P(i~F_+Wx1pw z)d}SjgoHPO&JZa4cEScFdPY)B+Ea3Wq$fXOaIC&*NC5k158GRz=+^@wE!WBRnL;^( zL*I7_5(x1n*1D7#R2Yb*#g5?`nUi^I>>T+qK(Nd*0N-9e zOJBmbMm1772Ee_ltWv=uphTCM_Y9yRr=e_dM-~?WXu!&*CS?SCv#!bKlvToiZ<{zB buW(=WzWV|ozh)x2WvF=ksq`HF_F|E zf4YafTpIv6%gPGP)MpU+0X(HnrD4k^%2N9k0k^p*^(Y?gC$l6NIcEtB2<+fGuse4S zz4PTcR6z_*u0htBbly?h?|c7Py{NYn_TDko@^Z;?Y72vOm7GQZWE7e6vDqTo&8U*9 z942}{{_j`?dkB?*+i?$$`{OYQy9vHfe@$PHk#;>=ot}g8>BMHU;Ra?yNEm=+pf?zr zn}jl#v9i6y_!R)1Cy4vvV;7cenxdX~KE%NnGG0P~pV9p#`jaKy3iD!a+Z$ zShz36T$`o437+x(>#&76{iq7>MZ|dPWh%nYUp(I9TumGI3nwe83qp5OPkCw`0*Bd? z^#NHF#m`2h>h#0FdTf=%EwEmaFDB!;lVSp=e@^0wT?zvnkT}rwANg@y_`{E~H;8&~ zuq7YuzPH$h29D_ogw1llf%E*z&r?Y4z4mEB)TI$VHOLdPk{tzK>MqpS_({X9^_iBO z?8TQTRbT9 z47O}D2u;^G?MN*y1!%5-UGGfX*dRe)Xz{yR&EpQR3^9kBL*7Y4;m8C((L5W6MKEco zsd5f^5*#tuJ|kD%r%#~p9D&hyA8*#uxu~-P7J)fSpH;5XYUH!O>ZR46OQXrwe>-yX zO7>K%4E#9vV1VClHOh6NROBzbZWqZKr8l*rn!4=tz;#AXcc|!DR*_~cZr&+@)~1&Q zUSvhSEY9j=LPW5`Pp$B@+yly)n+bxIf(>JeX89R%_|>_I5aNlL2{rkm9KMosBXsGV cAJ;odnuJ&o!t/dev/null # The certificate is from Let's Encrypt: -# 1 s:C=US, O=Let's Encrypt, CN=R11 +# 1 s:C=US, O=Let's Encrypt, CN=R10 # i:C=US, O=Internet Security Research Group, CN=ISRG Root X1 -# a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 +# a:PKEY: RSA, 2048 (bit); sigalg: sha256WithRSAEncryption # v:NotBefore: Mar 13 00:00:00 2024 GMT; NotAfter: Mar 12 23:59:59 2027 GMT -# Copy PEM content to a file (certmpy.pem) and convert to DER e.g. -# $ openssl x509 -in certmpy.pem -out certmpy.der -outform DER -# Then convert to hex format, eg using binascii.hexlify(data). +# Copy PEM content to a file (mpycert.pem) and convert to DER e.g. +# $ openssl x509 -in mpycert.pem -out mpycert.der -outform DER +# Then convert to hex format using: for i in range(0,len(data),40):print(data[i:i+40].hex()) -ca_cert_chain = binascii.unhexlify( - b"30820506308202eea0030201020211008a7d3e13d62f30ef2386bd29076b34f8300d06092a864886" - b"f70d01010b0500304f310b300906035504061302555331293027060355040a1320496e7465726e65" - b"742053656375726974792052657365617263682047726f7570311530130603550403130c49535247" - b"20526f6f74205831301e170d3234303331333030303030305a170d3237303331323233353935395a" - b"3033310b300906035504061302555331163014060355040a130d4c6574277320456e637279707431" - b"0c300a0603550403130352313130820122300d06092a864886f70d01010105000382010f00308201" - b"0a0282010100ba87bc5c1b0039cbca0acdd46710f9013ca54ea561cb26ca52fb1501b7b928f5281e" - b"ed27b324183967090c08ece03ab03b770ebdf3e53954410c4eae41d69974de51dbef7bff58bda8b7" - b"13f6de31d5f272c9726a0b8374959c4600641499f3b1d922d9cda892aa1c267a3ffeef58057b0895" - b"81db710f8efbe33109bb09be504d5f8f91763d5a9d9e83f2e9c466b3e106664348188065a037189a" - b"9b843297b1b2bdc4f815009d2788fbe26317966c9b27674bc4db285e69c279f0495ce02450e1c4bc" - b"a105ac7b406d00b4c2413fa758b82fc55c9ba5bb099ef1feebb08539fda80aef45c478eb652ac2cf" - b"5f3cdee35c4d1bf70b272baa0b4277534f796a1d87d90203010001a381f83081f5300e0603551d0f" - b"0101ff040403020186301d0603551d250416301406082b0601050507030206082b06010505070301" - b"30120603551d130101ff040830060101ff020100301d0603551d0e04160414c5cf46a4eaf4c3c07a" - b"6c95c42db05e922f26e3b9301f0603551d2304183016801479b459e67bb6e5e40173800888c81a58" - b"f6e99b6e303206082b0601050507010104263024302206082b060105050730028616687474703a2f" - b"2f78312e692e6c656e63722e6f72672f30130603551d20040c300a3008060667810c010201302706" - b"03551d1f0420301e301ca01aa0188616687474703a2f2f78312e632e6c656e63722e6f72672f300d" - b"06092a864886f70d01010b050003820201004ee2895d0a031c9038d0f51ff9715cf8c38fb237887a" - b"6fb0251fedbeb7d886068ee90984cd72bf81f3fccacf5348edbdf66942d4a5113e35c813b2921d05" - b"5fea2ed4d8f849c3adf599969cef26d8e1b4240b48204dfcd354b4a9c621c8e1361bff77642917b9" - b"f04bef5deacd79d0bf90bfbe23b290da4aa9483174a9440be1e2f62d8371a4757bd294c10519461c" - b"b98ff3c47448252a0de5f5db43e2db939bb919b41f2fdf6a0e8f31d3630fbb29dcdd662c3fb01b67" - b"51f8413ce44db9acb8a49c6663f5ab85231dcc53b6ab71aedcc50171da36ee0a182a32fd09317c8f" - b"f673e79c9cb54a156a77825acfda8d45fe1f2a6405303e73c2c60cb9d63b634aab4603fe99c04640" - b"276063df503a0747d8154a9fea471f995a08620cb66c33084dd738ed482d2e0568ae805def4cdcd8" - b"20415f68f1bb5acde30eb00c31879b43de4943e1c8043fd13c1b87453069a8a9720e79121c31d83e" - b"2357dda74fa0f01c81d1771f6fd6d2b9a8b3031681394b9f55aed26ae4b3bfeaa5d59f4ba3c9d63b" - b"72f34af654ab0cfc38f76080df6e35ca75a154e42fbc6e17c91aa537b5a29abaecf4c075464f77a8" - b"e8595691662d6ede2981d6a697055e6445be2cceea644244b0c34fadf0b4dc03ca999b098295820d" - b"638a66f91972f8d5b98910e289980935f9a21cbe92732374e99d1fd73b4a9a845810c2f3a7e235ec" - b"7e3b45ce3046526bc0c0" +ca_cert_chain = bytes.fromhex( + "30820505308202eda00302010202104ba85293f79a2fa273064ba8048d75d0300d06092a864886f7" + "0d01010b0500304f310b300906035504061302555331293027060355040a1320496e7465726e6574" + "2053656375726974792052657365617263682047726f7570311530130603550403130c4953524720" + "526f6f74205831301e170d3234303331333030303030305a170d3237303331323233353935395a30" + "33310b300906035504061302555331163014060355040a130d4c6574277320456e6372797074310c" + "300a0603550403130352313030820122300d06092a864886f70d01010105000382010f003082010a" + "0282010100cf57e5e6c45412edb447fec92758764650288c1d3e88df059dd5b51829bdddb55abffa" + "f6cea3beaf00214b625a5a3c012fc55803f689ff8e1143ebc1b5e01407968f6f1fd7e7ba81390975" + "65b7c2af185b372628e7a3f4072b6d1affab58bc95ae40ffe9cb57c4b55b7f780d1861bc17e754c6" + "bb4991cd6e18d18085eea66536bc74eabc504ceafc21f338169394bab0d36b3806cd16127aca5275" + "c8ad76b2c29c5d98455c6f617bc62dee3c13528601d957e6381cdf8db51f92919ae74a1ccc45a872" + "55f0b0e6a307ecfda71b669e3f488b71847158c93afaef5ef25b442b3c74e78fb247c1076acd9ab7" + "0d96f712812651540aec61f6f7f5e2f28ac8950d8d0203010001a381f83081f5300e0603551d0f01" + "01ff040403020186301d0603551d250416301406082b0601050507030206082b0601050507030130" + "120603551d130101ff040830060101ff020100301d0603551d0e04160414bbbcc347a5e4bca9c6c3" + "a4720c108da235e1c8e8301f0603551d2304183016801479b459e67bb6e5e40173800888c81a58f6" + "e99b6e303206082b0601050507010104263024302206082b060105050730028616687474703a2f2f" + "78312e692e6c656e63722e6f72672f30130603551d20040c300a3008060667810c01020130270603" + "551d1f0420301e301ca01aa0188616687474703a2f2f78312e632e6c656e63722e6f72672f300d06" + "092a864886f70d01010b0500038202010092b1e74137eb799d81e6cde225e13a20e9904495a3815c" + "cfc35dfdbda070d5b19628220bd2f228cf0ce7d4e6438c24221dc14292d109af9f4bf4c8704f2016" + "b15add01f61ff81f616b1427b0728d63aeeee2ce4bcf37ddbba3d4cde7ad50adbdbfe3ec3e623670" + "9931a7e88dddea62e212aef59cd43d2c0caad09c79beea3d5c446e9631635a7dd67e4f24a04b057f" + "5e6fd2d4ea5f334b13d657b6cade51b85da3098274fdc7789eb3b9ac16da4a2b96c3b68b628ff974" + "19a29e03dee96f9bb00fd2a05af6855cc204b7c8d54e32c4bf045dbc29f6f7818f0c5d3c53c94090" + "8bfbb60865b9a421d509e51384843782ce1028fc76c206257a46524dda5372a4273f6270acbe6948" + "00fb670fdb5ba1e8d703212dd7c9f69942398343df770a1208f125d6ba9419541888a5c58ee11a99" + "93796bec1cf93140b0cc3200df9f5ee7b492ab9082918d0de01e95ba593b2e4b5fc2b74635523906" + "c0bdaaac52c122a0449799f70ca021a7a16c714716170168c0caa62665047cb3aec9e79455c26f9b" + "3c1ca9f92ec5201af076e0beec18d64fd825fb7611e8bfe6210fe8e8ccb5b6a7d5b8f79f41cf6122" + "466a83b668972e7cea4e95db23eb2ec82b2884a460e949f4442e3bf9ca625701e25d9016f9c9fc7a" + "23488ea6d58172f128fa5dcefbed4e738f942ed241949899dba7af705ff5befb0220bf66276cb4ad" + "fa75120b2b3ece039e" ) diff --git a/tests/net_inet/test_sslcontext_client.py b/tests/net_inet/test_sslcontext_client.py index 30ec0ac7c83fc..119a42721fa3f 100644 --- a/tests/net_inet/test_sslcontext_client.py +++ b/tests/net_inet/test_sslcontext_client.py @@ -5,14 +5,12 @@ # This certificate was obtained from micropython.org using openssl: # $ openssl s_client -showcerts -connect micropython.org:443 /dev/null # The certificate is from Let's Encrypt: -# 1 s:C=US, O=Let's Encrypt, CN=R11 +# 1 s:C=US, O=Let's Encrypt, CN=R10 # i:C=US, O=Internet Security Research Group, CN=ISRG Root X1 -# a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 +# a:PKEY: RSA, 2048 (bit); sigalg: sha256WithRSAEncryption # v:NotBefore: Mar 13 00:00:00 2024 GMT; NotAfter: Mar 12 23:59:59 2027 GMT -# Copy PEM content to a file (certmpy.pem) and convert to DER e.g. -# $ openssl x509 -in certmpy.pem -out certmpy.der -outform DER -# Then convert to hex format, eg using binascii.hexlify(data). - +# Copy PEM content to a file (mpycert.pem) and convert to DER e.g. +# $ openssl x509 -in mpycert.pem -out mpycert.der -outform DER ca_cert_chain = "mpycert.der" try: From 487c94c2532ebb1b346afb92cb79a6b561e064be Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 7 May 2025 13:56:01 +1000 Subject: [PATCH 085/154] tests/extmod/vfs_rom.py: Clear sys.path before running test. Otherwise if the target has certain files/directories (such as "test") in its filesystem then these interfere with the unit tests. Signed-off-by: Damien George --- tests/extmod/vfs_rom.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/extmod/vfs_rom.py b/tests/extmod/vfs_rom.py index 770b6863b9c43..cd14542ea6b09 100644 --- a/tests/extmod/vfs_rom.py +++ b/tests/extmod/vfs_rom.py @@ -394,6 +394,7 @@ class TestMounted(TestBase): def setUp(self): self.orig_sys_path = list(sys.path) self.orig_cwd = os.getcwd() + sys.path = [] vfs.mount(vfs.VfsRom(self.romfs), "/test_rom") def tearDown(self): From 61eedbbd1126f895f729a1cf5f9e52e42ddcd471 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 10 Sep 2024 12:43:16 +1000 Subject: [PATCH 086/154] tools/verifygitlog.py: Apply stricter rules on git subject line. There is a bit of ambiguity as to how the prefix of the git subject line should look like. Eg `py/vm: ...` vs `py/vm.c: ...` (whether the extension should be there or not). This commit makes the existing CI check of the git commit message stricter, by applying extra rules to the prefix, the bit before the : in the subject line. It now checks that the subject prefix: - doesn't start with unwanted bits: ., /, ports/ - doesn't have an extension: .c, .h, .cpp, .js, .rst or .md Full error messages are given when a rule does not pass. This helps to reduce maintainer burden by applying stricter rules, to keep the git commit history consistent. Signed-off-by: Damien George --- tools/verifygitlog.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tools/verifygitlog.py b/tools/verifygitlog.py index 67215d5c5d066..5234611983be6 100755 --- a/tools/verifygitlog.py +++ b/tools/verifygitlog.py @@ -96,6 +96,9 @@ def verify_message_body(raw_body, err): if len(subject_line) >= 73: err.error("Subject line must be 72 or fewer characters: " + subject_line) + # Do additional checks on the prefix of the subject line. + verify_subject_line_prefix(subject_line.split(": ")[0], err) + # Second one divides subject and body. if len(raw_body) > 1 and raw_body[1]: err.error("Second message line must be empty: " + raw_body[1]) @@ -110,6 +113,26 @@ def verify_message_body(raw_body, err): err.error('Message must be signed-off. Use "git commit -s".') +def verify_subject_line_prefix(prefix, err): + ext = (".c", ".h", ".cpp", ".js", ".rst", ".md") + + if prefix.startswith("."): + err.error('Subject prefix cannot begin with ".".') + + if prefix.endswith("/"): + err.error('Subject prefix cannot end with "/".') + + if prefix.startswith("ports/"): + err.error( + 'Subject prefix cannot begin with "ports/", start with the name of the port instead.' + ) + + if prefix.endswith(ext): + err.error( + "Subject prefix cannot end with a file extension, use the main part of the filename without the extension." + ) + + def run(args): verbose("run", *args) From 26e978e7bc0b799d8b75d7cfe0219d2b788affa6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 7 May 2025 13:05:13 +1000 Subject: [PATCH 087/154] extmod/modlwip: Implement a queue of incoming UDP/raw packets. The bare-metal lwIP socket interface is currently quite limited when used for UDP streams, because it only allows one outstanding incoming UDP packet. If one UDP packet is waiting to be socket.recv'd and another one comes along, then the second one is simply dropped. This commit implements a queue for incoming UDP and raw packets. The queue depth is fixed at compile time, and is currently 4. This allows better use of UDP connections, eg more efficient. It also makes DTLS work better which sometimes has a queue of UDP packets (eg during the connection phase). Signed-off-by: Damien George --- extmod/modlwip.c | 167 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 114 insertions(+), 53 deletions(-) diff --git a/extmod/modlwip.c b/extmod/modlwip.c index f109e0029bb0f..961803f5e84dd 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -286,6 +286,15 @@ static const int error_lookup_table[] = { #define MOD_NETWORK_SOCK_DGRAM (2) #define MOD_NETWORK_SOCK_RAW (3) +// Total queue length for buffered UDP/raw incoming packets. +#define LWIP_INCOMING_PACKET_QUEUE_LEN (4) + +typedef struct _lwip_incoming_packet_t { + struct pbuf *pbuf; + ip_addr_t peer_addr; + uint16_t peer_port; +} lwip_incoming_packet_t; + typedef struct _lwip_socket_obj_t { mp_obj_base_t base; @@ -294,8 +303,11 @@ typedef struct _lwip_socket_obj_t { struct udp_pcb *udp; struct raw_pcb *raw; } pcb; + + // Data structure that holds incoming pbuf's. + // Each socket type has different state that it needs to keep track of. volatile union { - struct pbuf *pbuf; + // TCP listening sockets have a queue of incoming connections, implemented as a ringbuffer. struct { uint8_t alloc; uint8_t iget; @@ -305,10 +317,23 @@ typedef struct _lwip_socket_obj_t { struct tcp_pcb **array; // if alloc != 0 } tcp; } connection; + + // Connected TCP sockets have a single incoming pbuf that new data is appended to. + struct { + struct pbuf *pbuf; + } tcp; + + // UDP and raw sockets have a queue of incoming pbuf's, implemented as a ringbuffer. + struct { + uint8_t iget; // ringbuffer read index + uint8_t iput; // ringbuffer write index + lwip_incoming_packet_t *array; + } udp_raw; } incoming; + mp_obj_t callback; - ip_addr_t peer; - mp_uint_t peer_port; + ip_addr_t tcp_peer_addr; + mp_uint_t tcp_peer_port; mp_uint_t timeout; uint16_t recv_offset; @@ -347,9 +372,21 @@ static void lwip_socket_free_incoming(lwip_socket_obj_t *socket) { && socket->pcb.tcp->state == LISTEN; if (!socket_is_listener) { - if (socket->incoming.pbuf != NULL) { - pbuf_free(socket->incoming.pbuf); - socket->incoming.pbuf = NULL; + if (socket->type == MOD_NETWORK_SOCK_STREAM) { + if (socket->incoming.tcp.pbuf != NULL) { + pbuf_free(socket->incoming.tcp.pbuf); + socket->incoming.tcp.pbuf = NULL; + } + } else { + for (size_t i = 0; i < LWIP_INCOMING_PACKET_QUEUE_LEN; ++i) { + lwip_incoming_packet_t *slot = &socket->incoming.udp_raw.array[i]; + if (slot->pbuf != NULL) { + pbuf_free(slot->pbuf); + slot->pbuf = NULL; + } + } + socket->incoming.udp_raw.iget = 0; + socket->incoming.udp_raw.iput = 0; } } else { uint8_t alloc = socket->incoming.connection.alloc; @@ -407,6 +444,19 @@ static inline void exec_user_callback(lwip_socket_obj_t *socket) { } } +static void udp_raw_incoming(lwip_socket_obj_t *socket, struct pbuf *p, const ip_addr_t *addr, u16_t port) { + lwip_incoming_packet_t *slot = &socket->incoming.udp_raw.array[socket->incoming.udp_raw.iput]; + if (slot->pbuf != NULL) { + // No room in the inn, drop the packet. + pbuf_free(p); + } else { + slot->pbuf = p; + slot->peer_addr = *addr; + slot->peer_port = port; + socket->incoming.udp_raw.iput = (socket->incoming.udp_raw.iput + 1) % LWIP_INCOMING_PACKET_QUEUE_LEN; + } +} + #if MICROPY_PY_LWIP_SOCK_RAW // Callback for incoming raw packets. #if LWIP_VERSION_MAJOR < 2 @@ -416,13 +466,7 @@ static u8_t _lwip_raw_incoming(void *arg, struct raw_pcb *pcb, struct pbuf *p, c #endif { lwip_socket_obj_t *socket = (lwip_socket_obj_t *)arg; - - if (socket->incoming.pbuf != NULL) { - pbuf_free(p); - } else { - socket->incoming.pbuf = p; - memcpy(&socket->peer, addr, sizeof(socket->peer)); - } + udp_raw_incoming(socket, p, addr, 0); return 1; // we ate the packet } #endif @@ -436,15 +480,7 @@ static void _lwip_udp_incoming(void *arg, struct udp_pcb *upcb, struct pbuf *p, #endif { lwip_socket_obj_t *socket = (lwip_socket_obj_t *)arg; - - if (socket->incoming.pbuf != NULL) { - // That's why they call it "unreliable". No room in the inn, drop the packet. - pbuf_free(p); - } else { - socket->incoming.pbuf = p; - socket->peer_port = (mp_uint_t)port; - memcpy(&socket->peer, addr, sizeof(socket->peer)); - } + udp_raw_incoming(socket, p, addr, port); } // Callback for general tcp errors. @@ -562,13 +598,13 @@ static err_t _lwip_tcp_recv(void *arg, struct tcp_pcb *tcpb, struct pbuf *p, err return ERR_OK; } - if (socket->incoming.pbuf == NULL) { - socket->incoming.pbuf = p; + if (socket->incoming.tcp.pbuf == NULL) { + socket->incoming.tcp.pbuf = p; } else { #ifdef SOCKET_SINGLE_PBUF return ERR_BUF; #else - pbuf_cat(socket->incoming.pbuf, p); + pbuf_cat(socket->incoming.tcp.pbuf, p); #endif } @@ -639,7 +675,9 @@ static mp_uint_t lwip_raw_udp_send(lwip_socket_obj_t *socket, const byte *buf, m // Helper function for recv/recvfrom to handle raw/UDP packets static mp_uint_t lwip_raw_udp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_t len, ip_addr_t *ip, mp_uint_t *port, int *_errno) { - if (socket->incoming.pbuf == NULL) { + lwip_incoming_packet_t *slot = &socket->incoming.udp_raw.array[socket->incoming.udp_raw.iget]; + + if (slot->pbuf == NULL) { if (socket->timeout == 0) { // Non-blocking socket. *_errno = MP_EAGAIN; @@ -648,7 +686,7 @@ static mp_uint_t lwip_raw_udp_receive(lwip_socket_obj_t *socket, byte *buf, mp_u // Wait for data to arrive on UDP socket. mp_uint_t start = mp_hal_ticks_ms(); - while (socket->incoming.pbuf == NULL) { + while (slot->pbuf == NULL) { if (socket->timeout != -1 && mp_hal_ticks_ms() - start > socket->timeout) { *_errno = MP_ETIMEDOUT; return -1; @@ -658,17 +696,18 @@ static mp_uint_t lwip_raw_udp_receive(lwip_socket_obj_t *socket, byte *buf, mp_u } if (ip != NULL) { - memcpy(ip, &socket->peer, sizeof(socket->peer)); - *port = socket->peer_port; + *ip = slot->peer_addr; + *port = slot->peer_port; } - struct pbuf *p = socket->incoming.pbuf; + struct pbuf *p = slot->pbuf; MICROPY_PY_LWIP_ENTER u16_t result = pbuf_copy_partial(p, buf, ((p->tot_len > len) ? len : p->tot_len), 0); pbuf_free(p); - socket->incoming.pbuf = NULL; + slot->pbuf = NULL; + socket->incoming.udp_raw.iget = (socket->incoming.udp_raw.iget + 1) % LWIP_INCOMING_PACKET_QUEUE_LEN; MICROPY_PY_LWIP_EXIT @@ -780,7 +819,7 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ // Check for any pending errors STREAM_ERROR_CHECK(socket); - if (socket->incoming.pbuf == NULL) { + if (socket->incoming.tcp.pbuf == NULL) { // Non-blocking socket if (socket->timeout == 0) { @@ -792,7 +831,7 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ } mp_uint_t start = mp_hal_ticks_ms(); - while (socket->state == STATE_CONNECTED && socket->incoming.pbuf == NULL) { + while (socket->state == STATE_CONNECTED && socket->incoming.tcp.pbuf == NULL) { if (socket->timeout != -1 && mp_hal_ticks_ms() - start > socket->timeout) { *_errno = MP_ETIMEDOUT; return -1; @@ -801,7 +840,7 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ } if (socket->state == STATE_PEER_CLOSED) { - if (socket->incoming.pbuf == NULL) { + if (socket->incoming.tcp.pbuf == NULL) { // socket closed and no data left in buffer return 0; } @@ -819,7 +858,7 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ assert(socket->pcb.tcp != NULL); - struct pbuf *p = socket->incoming.pbuf; + struct pbuf *p = socket->incoming.tcp.pbuf; mp_uint_t remaining = p->len - socket->recv_offset; if (len > remaining) { @@ -830,7 +869,7 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ remaining -= len; if (remaining == 0) { - socket->incoming.pbuf = p->next; + socket->incoming.tcp.pbuf = p->next; // If we don't ref here, free() will free the entire chain, // if we ref, it does what we need: frees 1st buf, and decrements // next buf's refcount back to 1. @@ -854,8 +893,18 @@ static const mp_obj_type_t lwip_socket_type; static void lwip_socket_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { lwip_socket_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "", self->state, self->timeout, - self->incoming.pbuf, self->recv_offset); + mp_printf(print, "", self->incoming.tcp.pbuf, self->recv_offset); + } else { + int num_in_queue = 0; + for (size_t i = 0; i < LWIP_INCOMING_PACKET_QUEUE_LEN; ++i) { + if (self->incoming.udp_raw.array[i].pbuf != NULL) { + ++num_in_queue; + } + } + mp_printf(print, "%d>", num_in_queue); + } } // FIXME: Only supports two arguments at present @@ -884,16 +933,22 @@ static mp_obj_t lwip_socket_make_new(const mp_obj_type_t *type, size_t n_args, s socket->incoming.connection.tcp.item = NULL; break; case MOD_NETWORK_SOCK_DGRAM: - socket->pcb.udp = udp_new(); - socket->incoming.pbuf = NULL; - break; #if MICROPY_PY_LWIP_SOCK_RAW - case MOD_NETWORK_SOCK_RAW: { - mp_int_t proto = n_args <= 2 ? 0 : mp_obj_get_int(args[2]); - socket->pcb.raw = raw_new(proto); - break; - } + case MOD_NETWORK_SOCK_RAW: #endif + if (socket->type == MOD_NETWORK_SOCK_DGRAM) { + socket->pcb.udp = udp_new(); + } + #if MICROPY_PY_LWIP_SOCK_RAW + else { + mp_int_t proto = n_args <= 2 ? 0 : mp_obj_get_int(args[2]); + socket->pcb.raw = raw_new(proto); + } + #endif + socket->incoming.udp_raw.iget = 0; + socket->incoming.udp_raw.iput = 0; + socket->incoming.udp_raw.array = m_new0(lwip_incoming_packet_t, LWIP_INCOMING_PACKET_QUEUE_LEN); + break; default: mp_raise_OSError(MP_EINVAL); } @@ -1075,7 +1130,7 @@ static mp_obj_t lwip_socket_accept(mp_obj_t self_in) { // ...and set up the new socket for it. socket2->domain = MOD_NETWORK_AF_INET; socket2->type = MOD_NETWORK_SOCK_STREAM; - socket2->incoming.pbuf = NULL; + socket2->incoming.tcp.pbuf = NULL; socket2->timeout = socket->timeout; socket2->state = STATE_CONNECTED; socket2->recv_offset = 0; @@ -1130,8 +1185,8 @@ static mp_obj_t lwip_socket_connect(mp_obj_t self_in, mp_obj_t addr_in) { socket->state = STATE_NEW; mp_raise_OSError(error_lookup_table[-err]); } - socket->peer_port = (mp_uint_t)port; - memcpy(&socket->peer, &dest, sizeof(socket->peer)); + socket->tcp_peer_addr = dest; + socket->tcp_peer_port = (mp_uint_t)port; MICROPY_PY_LWIP_EXIT // And now we wait... @@ -1299,8 +1354,8 @@ static mp_obj_t lwip_socket_recvfrom(mp_obj_t self_in, mp_obj_t len_in) { mp_uint_t ret = 0; switch (socket->type) { case MOD_NETWORK_SOCK_STREAM: { - memcpy(&ip, &socket->peer, sizeof(socket->peer)); - port = (mp_uint_t)socket->peer_port; + ip = socket->tcp_peer_addr; + port = (mp_uint_t)socket->tcp_peer_port; ret = lwip_tcp_receive(socket, (byte *)vstr.buf, len, &_errno); break; } @@ -1537,9 +1592,15 @@ static mp_uint_t lwip_socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_ if (lwip_socket_incoming_array(socket)[socket->incoming.connection.iget] != NULL) { ret |= MP_STREAM_POLL_RD; } + } else if (socket->type == MOD_NETWORK_SOCK_STREAM) { + // For TCP sockets there is just one slot for incoming data + if (socket->incoming.tcp.pbuf != NULL) { + ret |= MP_STREAM_POLL_RD; + } } else { - // Otherwise there is just one slot for incoming data - if (socket->incoming.pbuf != NULL) { + // Otherwise for UDP/raw there is a queue of incoming data + lwip_incoming_packet_t *slot = &socket->incoming.udp_raw.array[socket->incoming.udp_raw.iget]; + if (slot->pbuf != NULL) { ret |= MP_STREAM_POLL_RD; } } From a05766f47b34136a405a90f9c670f8f886c2e0b4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 7 May 2025 13:06:00 +1000 Subject: [PATCH 088/154] tests/multi_net: Add test that requires queuing UDP packets. This commit adds a new network multi-test which sends a burst of UDP packets from the client, and the server doesn't recv them until they have all been sent. Signed-off-by: Damien George --- tests/multi_net/udp_data_multi.py | 69 +++++++++++++++++++++++++++ tests/multi_net/udp_data_multi.py.exp | 15 ++++++ 2 files changed, 84 insertions(+) create mode 100644 tests/multi_net/udp_data_multi.py create mode 100644 tests/multi_net/udp_data_multi.py.exp diff --git a/tests/multi_net/udp_data_multi.py b/tests/multi_net/udp_data_multi.py new file mode 100644 index 0000000000000..5d7b13e5188dd --- /dev/null +++ b/tests/multi_net/udp_data_multi.py @@ -0,0 +1,69 @@ +# Test UDP reception when there are multiple incoming UDP packets that need to be +# queued internally in the TCP/IP stack. + +import socket + +NUM_NEW_SOCKETS = 4 +NUM_PACKET_BURSTS = 6 +NUM_PACKET_GROUPS = 5 +TOTAL_PACKET_BURSTS = NUM_NEW_SOCKETS * NUM_PACKET_BURSTS +# The tast passes if more than 75% of packets are received in each group. +PACKET_RECV_THRESH = 0.75 * TOTAL_PACKET_BURSTS +PORT = 8000 + + +# Server +def instance0(): + recv_count = {i: 0 for i in range(NUM_PACKET_GROUPS)} + multitest.globals(IP=multitest.get_network_ip()) + multitest.next() + for i in range(NUM_NEW_SOCKETS): + print("test socket", i) + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(socket.getaddrinfo("0.0.0.0", PORT + i)[0][-1]) + s.settimeout(0.250) + multitest.broadcast("server ready") + for burst in range(NUM_PACKET_BURSTS): + # Wait for all packets to be sent, without receiving any yet. + multitest.wait("data sent burst={}".format(burst)) + # Try to receive all packets (they should be waiting in the queue). + for group in range(NUM_PACKET_GROUPS): + try: + data, addr = s.recvfrom(1000) + except: + continue + recv_burst, recv_group = data.split(b":") + recv_burst = int(recv_burst) + recv_group = int(recv_group) + if recv_burst == burst: + recv_count[recv_group] += 1 + # Inform the client that all data was received. + multitest.broadcast("data received burst={}".format(burst)) + s.close() + + # Check how many packets were received. + for group, count in recv_count.items(): + if count >= PACKET_RECV_THRESH: + print("pass group={}".format(group)) + else: + print("fail group={} received={}%".format(group, 100 * count // TOTAL_PACKET_BURSTS)) + + +# Client +def instance1(): + multitest.next() + for i in range(NUM_NEW_SOCKETS): + print("test socket", i) + ai = socket.getaddrinfo(IP, PORT + i)[0][-1] + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + multitest.wait("server ready") + for burst in range(NUM_PACKET_BURSTS): + # Send a bunch of packets all in a row. + for group in range(NUM_PACKET_GROUPS): + s.sendto(b"%d:%d" % (burst, group), ai) + # Inform the server that the data has been sent. + multitest.broadcast("data sent burst={}".format(burst)) + # Wait for the server to finish receiving. + multitest.wait("data received burst={}".format(burst)) + s.close() diff --git a/tests/multi_net/udp_data_multi.py.exp b/tests/multi_net/udp_data_multi.py.exp new file mode 100644 index 0000000000000..bc67c6ab0cf01 --- /dev/null +++ b/tests/multi_net/udp_data_multi.py.exp @@ -0,0 +1,15 @@ +--- instance0 --- +test socket 0 +test socket 1 +test socket 2 +test socket 3 +pass group=0 +pass group=1 +pass group=2 +pass group=3 +pass group=4 +--- instance1 --- +test socket 0 +test socket 1 +test socket 2 +test socket 3 From ee2c78cd972ddd91543d2e73b81b83895ca4cfae Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Mon, 30 Sep 2024 13:05:22 +0100 Subject: [PATCH 089/154] rp2: Use pico-sdk alarm pool instead of soft timer for sleep. Stop using soft timer for `mp_wfe_or_timeout`. Now uses the alarm pool again as issues with this code have been fixed. This resolves the "sev" issue that stops the RP2350 going idle. Also, change the lightsleep code to use the hardware timer library and alarm 1, as alarm 2 is used by and soft timers and alarm 3 is used by the alarm pool. Signed-off-by: Peter Harper --- ports/rp2/modmachine.c | 35 ++++++++++++++++++++--------------- ports/rp2/mpconfigport.h | 3 ++- ports/rp2/mphalport.c | 12 +----------- 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 58a3a8ae4d87d..742d20f1f8a4c 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -137,6 +137,9 @@ static void mp_machine_idle(void) { MICROPY_INTERNAL_WFE(1); } +static void alarm_sleep_callback(uint alarm_id) { +} + static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { mp_int_t delay_ms = 0; bool use_timer_alarm = false; @@ -206,6 +209,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { // Disable ROSC. rosc_hw->ctrl = ROSC_CTRL_ENABLE_VALUE_DISABLE << ROSC_CTRL_ENABLE_LSB; + bool alarm_armed = false; if (n_args == 0) { #if MICROPY_PY_NETWORK_CYW43 gpio_set_dormant_irq_enabled(CYW43_PIN_WL_HOST_WAKE, GPIO_IRQ_LEVEL_HIGH, true); @@ -214,16 +218,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { } else { uint32_t save_sleep_en0 = clocks_hw->sleep_en0; uint32_t save_sleep_en1 = clocks_hw->sleep_en1; - bool timer3_enabled = irq_is_enabled(3); - - const uint32_t alarm_num = 3; - const uint32_t irq_num = TIMER_ALARM_IRQ_NUM(timer_hw, alarm_num); if (use_timer_alarm) { - // Make sure ALARM3/IRQ3 is enabled on _this_ core - if (!timer3_enabled) { - irq_set_enabled(irq_num, true); - } - hw_set_bits(&timer_hw->inte, 1u << alarm_num); // Use timer alarm to wake. clocks_hw->sleep_en0 = 0x0; #if PICO_RP2040 @@ -233,8 +228,11 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { #else #error Unknown processor #endif - timer_hw->intr = 1u << alarm_num; // clear any IRQ - timer_hw->alarm[alarm_num] = timer_hw->timerawl + delay_ms * 1000; + hardware_alarm_claim(MICROPY_HW_LIGHTSLEEP_ALARM_NUM); + hardware_alarm_set_callback(MICROPY_HW_LIGHTSLEEP_ALARM_NUM, alarm_sleep_callback); + if (hardware_alarm_set_target(MICROPY_HW_LIGHTSLEEP_ALARM_NUM, make_timeout_time_ms(delay_ms)) == PICO_OK) { + alarm_armed = true; + } } else { // TODO: Use RTC alarm to wake. clocks_hw->sleep_en0 = 0x0; @@ -264,10 +262,8 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { #endif // Go into low-power mode. - __wfi(); - - if (!timer3_enabled) { - irq_set_enabled(irq_num, false); + if (alarm_armed) { + __wfi(); } clocks_hw->sleep_en0 = save_sleep_en0; clocks_hw->sleep_en1 = save_sleep_en1; @@ -282,6 +278,15 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { // Re-sync mp_hal_time_ns() counter with aon timer. mp_hal_time_ns_set_from_rtc(); + + // Note: This must be done after MICROPY_END_ATOMIC_SECTION + if (use_timer_alarm) { + if (alarm_armed) { + hardware_alarm_cancel(MICROPY_HW_LIGHTSLEEP_ALARM_NUM); + } + hardware_alarm_set_callback(MICROPY_HW_LIGHTSLEEP_ALARM_NUM, NULL); + hardware_alarm_unclaim(MICROPY_HW_LIGHTSLEEP_ALARM_NUM); + } } NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 3d65737266b49..877cea08717fd 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -198,8 +198,9 @@ #define MICROPY_PY_LWIP_SOCK_RAW (MICROPY_PY_LWIP) // Hardware timer alarm index. Available range 0-3. -// Number 3 is currently used by pico-sdk (PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM) +// Number 3 is currently used by pico-sdk alarm pool (PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM) #define MICROPY_HW_SOFT_TIMER_ALARM_NUM (2) +#define MICROPY_HW_LIGHTSLEEP_ALARM_NUM (1) // fatfs configuration #define MICROPY_FATFS_ENABLE_LFN (2) diff --git a/ports/rp2/mphalport.c b/ports/rp2/mphalport.c index caecb695099d5..b581b3b59f95d 100644 --- a/ports/rp2/mphalport.c +++ b/ports/rp2/mphalport.c @@ -266,17 +266,7 @@ void soft_timer_init(void) { } void mp_wfe_or_timeout(uint32_t timeout_ms) { - soft_timer_entry_t timer; - - // Note the timer doesn't have an associated callback, it just exists to create a - // hardware interrupt to wake the CPU - soft_timer_static_init(&timer, SOFT_TIMER_MODE_ONE_SHOT, 0, NULL); - soft_timer_insert(&timer, timeout_ms); - - __wfe(); - - // Clean up the timer node if it's not already - soft_timer_remove(&timer); + best_effort_wfe_or_timeout(delayed_by_ms(get_absolute_time(), timeout_ms)); } int mp_hal_is_pin_reserved(int n) { From 2a4f1c9f0fd4f582cb22b25e6fcf7521bfa4f268 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Tue, 18 Mar 2025 15:32:02 +0000 Subject: [PATCH 090/154] rp2/modmachine: Add debug code for mp_machine_lightsleep. Add some debug code that can be enabled to determine why lightsleep is returning early. Signed-off-by: Peter Harper --- ports/rp2/modmachine.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 742d20f1f8a4c..fd2912c80f828 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -140,6 +140,9 @@ static void mp_machine_idle(void) { static void alarm_sleep_callback(uint alarm_id) { } +// Set this to 1 to enable some debug of the interrupt that woke the device +#define DEBUG_LIGHTSLEEP 0 + static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { mp_int_t delay_ms = 0; bool use_timer_alarm = false; @@ -209,6 +212,14 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { // Disable ROSC. rosc_hw->ctrl = ROSC_CTRL_ENABLE_VALUE_DISABLE << ROSC_CTRL_ENABLE_LSB; + #if DEBUG_LIGHTSLEEP + #if PICO_RP2040 + uint32_t pending_intr = 0; + #else + uint32_t pending_intr[2] = { 0 }; + #endif + #endif + bool alarm_armed = false; if (n_args == 0) { #if MICROPY_PY_NETWORK_CYW43 @@ -264,6 +275,15 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { // Go into low-power mode. if (alarm_armed) { __wfi(); + + #if DEBUG_LIGHTSLEEP + #if PICO_RP2040 + pending_intr = nvic_hw->ispr; + #else + pending_intr[0] = nvic_hw->ispr[0]; + pending_intr[1] = nvic_hw->ispr[1]; + #endif + #endif } clocks_hw->sleep_en0 = save_sleep_en0; clocks_hw->sleep_en1 = save_sleep_en1; @@ -286,6 +306,19 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { } hardware_alarm_set_callback(MICROPY_HW_LIGHTSLEEP_ALARM_NUM, NULL); hardware_alarm_unclaim(MICROPY_HW_LIGHTSLEEP_ALARM_NUM); + + #if DEBUG_LIGHTSLEEP + // Check irq.h for the list of IRQ's + // for rp2040 00000042: TIMER_IRQ_1 woke the device as expected + // 00000020: USBCTRL_IRQ woke the device (probably early) + // For rp2350 00000000:00000002: TIMER0_IRQ_1 woke the device as expected + // 00000000:00004000: USBCTRL_IRQ woke the device (probably early) + #if PICO_RP2040 + mp_printf(MP_PYTHON_PRINTER, "lightsleep: pending_intr=%08lx\n", pending_intr); + #else + mp_printf(MP_PYTHON_PRINTER, "lightsleep: pending_intr=%08lx:%08lx\n", pending_intr[1], pending_intr[0]); + #endif + #endif } } From 03da15575f088085e66be641c7e5ffd5cb2cc318 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Tue, 18 Mar 2025 17:45:48 +0000 Subject: [PATCH 091/154] tests/ports/rp2: Update machine idle test to revert skip for RP2350. This reverts commit b42bb911c663dc90575d6a7fe3ea4760b6559372. Signed-off-by: Peter Harper --- tests/ports/rp2/rp2_machine_idle.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/ports/rp2/rp2_machine_idle.py b/tests/ports/rp2/rp2_machine_idle.py index 3135110b82000..f9c28284782f8 100644 --- a/tests/ports/rp2/rp2_machine_idle.py +++ b/tests/ports/rp2/rp2_machine_idle.py @@ -1,4 +1,3 @@ -import sys import machine import time @@ -18,11 +17,6 @@ # Verification uses the average idle time, as individual iterations will always # have outliers due to interrupts, scheduler, etc. -# RP2350 currently fails this test because machine.idle() resumes immediately. -if "RP2350" in sys.implementation._machine: - print("SKIP") - raise SystemExit - ITERATIONS = 500 total = 0 From 977fd94856dfb909b816a93e9d962d4c467f253a Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 18 Dec 2024 16:27:48 +1100 Subject: [PATCH 092/154] tests/ports/rp2: Add a test case for light sleeping from CPU1. Not currently passing. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/ports/rp2/rp2_lightsleep_thread.py | 54 ++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 tests/ports/rp2/rp2_lightsleep_thread.py diff --git a/tests/ports/rp2/rp2_lightsleep_thread.py b/tests/ports/rp2/rp2_lightsleep_thread.py new file mode 100644 index 0000000000000..2aaa34bbe3989 --- /dev/null +++ b/tests/ports/rp2/rp2_lightsleep_thread.py @@ -0,0 +1,54 @@ +# Verify that a thread running on CPU1 can go to lightsleep +# and wake up in the expected timeframe +import _thread +import time +import unittest +from machine import lightsleep + +N_SLEEPS = 5 +SLEEP_MS = 250 + +IDEAL_RUNTIME = N_SLEEPS * SLEEP_MS +MAX_RUNTIME = (N_SLEEPS + 1) * SLEEP_MS +MAX_DELTA = 20 + + +class LightSleepInThread(unittest.TestCase): + def thread_entry(self, is_thread=True): + for _ in range(N_SLEEPS): + lightsleep(SLEEP_MS) + if is_thread: + self.thread_done = True + + def elapsed_ms(self): + return time.ticks_diff(time.ticks_ms(), self.t0) + + def setUp(self): + self.thread_done = False + self.t0 = time.ticks_ms() + + def test_cpu0_busy(self): + _thread.start_new_thread(self.thread_entry, ()) + # CPU0 is busy-waiting not asleep itself + while not self.thread_done: + self.assertLessEqual(self.elapsed_ms(), MAX_RUNTIME) + self.assertAlmostEqual(self.elapsed_ms(), IDEAL_RUNTIME, delta=MAX_DELTA) + + def test_cpu0_sleeping(self): + _thread.start_new_thread(self.thread_entry, ()) + time.sleep_ms(MAX_RUNTIME) + self.assertTrue(self.thread_done) + self.assertAlmostEqual(self.elapsed_ms(), MAX_RUNTIME, delta=MAX_DELTA) + + def test_cpu0_also_lightsleep(self): + _thread.start_new_thread(self.thread_entry, ()) + time.sleep(0.050) # account for any delay in starting the thread + self.thread_entry(False) # does the same lightsleep loop, doesn't set the done flag + self.assertTrue(self.thread_done) + # only one thread can actually be in lightsleep at a time to avoid races, so the total + # runtime is doubled by doing it on both CPUs + self.assertAlmostEqual(self.elapsed_ms(), IDEAL_RUNTIME * 2, delta=IDEAL_RUNTIME) + + +if __name__ == "__main__": + unittest.main() From 69993daa5c2eae6055f0b3b2330e95e78d6e3738 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 9 May 2025 15:59:16 +1000 Subject: [PATCH 093/154] rp2/modmachine: Add mutual exclusion for machine.lightsleep(). There's no specified behaviour for what should happen if both CPUs call `lightsleep()` together, but the latest changes could cause a permanent hang due to a race in the timer cleanup code. Add a flag to prevent hangs if two threads accidentally lightsleep, at least. This allows the new lightsleep test to pass on RPI_PICO and RPI_PICO2, and even have much tighter time deltas. However, the test still fails on wireless boards where the lwIP tick wakes them up too frequently. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/rp2/modmachine.c | 16 ++++++++++++++++ tests/ports/rp2/rp2_lightsleep_thread.py | 23 ++++++++++++++++++----- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index fd2912c80f828..3625d404cdc7b 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -173,6 +173,16 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { } #endif + #if MICROPY_PY_THREAD + static bool in_lightsleep; + if (in_lightsleep) { + // The other CPU is also in machine.lightsleep() + MICROPY_END_ATOMIC_SECTION(my_interrupts); + return; + } + in_lightsleep = true; + #endif + #if MICROPY_HW_ENABLE_USBDEV // Only disable the USB clock if a USB host has not configured the device // or if going to DORMANT mode. @@ -320,6 +330,12 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { #endif #endif } + + #if MICROPY_PY_THREAD + // Clearing the flag here is atomic, and we know we're the ones who set it + // (higher up, inside the critical section) + in_lightsleep = false; + #endif } NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { diff --git a/tests/ports/rp2/rp2_lightsleep_thread.py b/tests/ports/rp2/rp2_lightsleep_thread.py index 2aaa34bbe3989..494ead422315b 100644 --- a/tests/ports/rp2/rp2_lightsleep_thread.py +++ b/tests/ports/rp2/rp2_lightsleep_thread.py @@ -42,12 +42,25 @@ def test_cpu0_sleeping(self): def test_cpu0_also_lightsleep(self): _thread.start_new_thread(self.thread_entry, ()) - time.sleep(0.050) # account for any delay in starting the thread + time.sleep_ms(50) # account for any delay in starting the thread self.thread_entry(False) # does the same lightsleep loop, doesn't set the done flag - self.assertTrue(self.thread_done) - # only one thread can actually be in lightsleep at a time to avoid races, so the total - # runtime is doubled by doing it on both CPUs - self.assertAlmostEqual(self.elapsed_ms(), IDEAL_RUNTIME * 2, delta=IDEAL_RUNTIME) + while not self.thread_done: + time.sleep_ms(10) + # + # Only one thread can actually be in lightsleep at a time to avoid + # races, but otherwise the behaviour when both threads call lightsleep() + # is unspecified. + # + # Currently, the other thread will return immediately if one is already + # in lightsleep. Therefore, runtime can be between IDEAL_RUNTIME and + # IDEAL_RUNTIME * 2 depending on how many times the calls to lightsleep() race + # each other. + # + # Note this test case is really only here to ensure that the rp2 hasn't + # hung or failed to sleep at all - not to verify any correct behaviour + # when there's a race to call lightsleep(). + self.assertGreaterEqual(self.elapsed_ms(), IDEAL_RUNTIME - MAX_DELTA) + self.assertLessEqual(self.elapsed_ms(), IDEAL_RUNTIME * 2 + MAX_DELTA) if __name__ == "__main__": From f47e214cdcf11c2067936cb4b4e4f9deab73f6fc Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 15 Jan 2025 16:25:55 +0100 Subject: [PATCH 094/154] all: Rename the "NORETURN" macro to "MP_NORETURN". This commit renames the NORETURN macro, indicating to the compiler that a function does not return, into MP_NORETURN to maintain the same naming convention of other similar macros. To maintain compaitiblity with existing code NORETURN is aliased to MP_NORETURN, but it is also deprecated for MicroPython v2. This changeset was created using a similar process to decf8e6a8bb940d5829ca3296790631fcece7b21 ("all: Remove the "STATIC" macro and just use "static" instead."), with no documentation or python scripts to change to reflect the new macro name. Signed-off-by: Alessandro Gatti --- extmod/modmachine.c | 12 ++++----- extmod/modmachine.h | 2 +- extmod/modtls_axtls.c | 2 +- extmod/modtls_mbedtls.c | 2 +- extmod/moductypes.c | 2 +- mpy-cross/mpconfigport.h | 2 +- ports/alif/main.c | 2 +- ports/alif/modmachine.c | 6 ++--- ports/cc3200/misc/mperror.c | 2 +- ports/cc3200/misc/mperror.h | 2 +- ports/cc3200/mods/modmachine.c | 4 +-- ports/cc3200/mods/pybsleep.c | 4 +-- ports/esp32/modmachine.c | 8 +++--- ports/esp32/modnetwork.h | 2 +- ports/esp32/network_common.c | 2 +- ports/esp8266/esp_init_data.c | 2 +- ports/esp8266/modmachine.c | 4 +-- ports/mimxrt/modmachine.c | 6 ++--- ports/minimal/main.c | 2 +- ports/nrf/main.c | 4 +-- ports/nrf/modules/machine/modmachine.c | 6 ++--- ports/nrf/mphalport.c | 2 +- ports/nrf/mphalport.h | 2 +- ports/pic16bit/main.c | 2 +- ports/powerpc/main.c | 2 +- ports/qemu/mcu/arm/errorhandler.c | 20 +++++++-------- ports/qemu/mcu/arm/startup.c | 2 +- ports/renesas-ra/main.c | 2 +- ports/renesas-ra/modmachine.c | 6 ++--- ports/renesas-ra/mphalport.c | 2 +- ports/renesas-ra/mphalport.h | 2 +- ports/renesas-ra/powerctrl.c | 6 ++--- ports/renesas-ra/powerctrl.h | 6 ++--- ports/renesas-ra/uart.c | 2 +- ports/rp2/modmachine.c | 6 ++--- ports/samd/modmachine.c | 6 ++--- ports/stm32/boardctrl.c | 2 +- ports/stm32/boardctrl.h | 2 +- ports/stm32/mboot/main.c | 4 +-- ports/stm32/mboot/mboot.h | 2 +- ports/stm32/modmachine.c | 4 +-- ports/stm32/mphalport.c | 2 +- ports/stm32/mphalport.h | 2 +- ports/stm32/powerctrl.c | 8 +++--- ports/stm32/powerctrl.h | 6 ++--- ports/webassembly/main.c | 2 +- ports/windows/mpconfigport.h | 2 +- ports/zephyr/main.c | 2 +- py/argcheck.c | 4 +-- py/bc.c | 2 +- py/dynruntime.h | 4 +-- py/misc.h | 2 +- py/modmath.c | 2 +- py/mpconfig.h | 8 ++++-- py/nativeglue.h | 2 +- py/nlr.c | 2 +- py/nlr.h | 6 ++--- py/nlraarch64.c | 2 +- py/nlrmips.c | 2 +- py/nlrpowerpc.c | 4 +-- py/nlrrv32.c | 2 +- py/nlrrv64.c | 2 +- py/nlrthumb.c | 2 +- py/nlrx64.c | 2 +- py/nlrx86.c | 2 +- py/nlrxtensa.c | 2 +- py/objstr.c | 6 ++--- py/parsenum.c | 2 +- py/runtime.c | 32 ++++++++++++------------ py/runtime.h | 34 +++++++++++++------------- shared/libc/abort_.c | 4 +-- 71 files changed, 159 insertions(+), 155 deletions(-) diff --git a/extmod/modmachine.c b/extmod/modmachine.c index 5906835949861..f2570123e3751 100644 --- a/extmod/modmachine.c +++ b/extmod/modmachine.c @@ -45,11 +45,11 @@ static void mp_machine_idle(void); #if MICROPY_PY_MACHINE_BOOTLOADER -NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args); +MP_NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args); #endif #if MICROPY_PY_MACHINE_RESET -NORETURN static void mp_machine_reset(void); +MP_NORETURN static void mp_machine_reset(void); static mp_int_t mp_machine_reset_cause(void); #endif @@ -58,7 +58,7 @@ static mp_obj_t mp_machine_unique_id(void); static mp_obj_t mp_machine_get_freq(void); static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args); static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args); -NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args); +MP_NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args); #endif // The port can provide additional machine-module implementation in this file. @@ -67,7 +67,7 @@ NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args); #endif #if MICROPY_PY_MACHINE_BOOTLOADER -NORETURN mp_obj_t machine_bootloader(size_t n_args, const mp_obj_t *args) { +MP_NORETURN mp_obj_t machine_bootloader(size_t n_args, const mp_obj_t *args) { mp_machine_bootloader(n_args, args); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_bootloader_obj, 0, 1, machine_bootloader); @@ -81,7 +81,7 @@ static MP_DEFINE_CONST_FUN_OBJ_0(machine_idle_obj, machine_idle); #if MICROPY_PY_MACHINE_RESET -NORETURN static mp_obj_t machine_reset(void) { +MP_NORETURN static mp_obj_t machine_reset(void) { mp_machine_reset(); } MP_DEFINE_CONST_FUN_OBJ_0(machine_reset_obj, machine_reset); @@ -116,7 +116,7 @@ static mp_obj_t machine_lightsleep(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_lightsleep_obj, 0, 1, machine_lightsleep); -NORETURN static mp_obj_t machine_deepsleep(size_t n_args, const mp_obj_t *args) { +MP_NORETURN static mp_obj_t machine_deepsleep(size_t n_args, const mp_obj_t *args) { mp_machine_deepsleep(n_args, args); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_deepsleep_obj, 0, 1, machine_deepsleep); diff --git a/extmod/modmachine.h b/extmod/modmachine.h index 7c16ed302ee2f..26010be8e18f9 100644 --- a/extmod/modmachine.h +++ b/extmod/modmachine.h @@ -242,7 +242,7 @@ uintptr_t MICROPY_MACHINE_MEM_GET_READ_ADDR(mp_obj_t addr_o, uint align); uintptr_t MICROPY_MACHINE_MEM_GET_WRITE_ADDR(mp_obj_t addr_o, uint align); #endif -NORETURN mp_obj_t machine_bootloader(size_t n_args, const mp_obj_t *args); +MP_NORETURN mp_obj_t machine_bootloader(size_t n_args, const mp_obj_t *args); void machine_bitstream_high_low(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len); mp_uint_t machine_time_pulse_us(mp_hal_pin_obj_t pin, int pulse_level, mp_uint_t timeout_us); diff --git a/extmod/modtls_axtls.c b/extmod/modtls_axtls.c index 0e49fde2d84c5..ba28c13fce212 100644 --- a/extmod/modtls_axtls.c +++ b/extmod/modtls_axtls.c @@ -105,7 +105,7 @@ static const char *const ssl_error_tab2[] = { "NOT_SUPPORTED", }; -static NORETURN void ssl_raise_error(int err) { +static MP_NORETURN void ssl_raise_error(int err) { MP_STATIC_ASSERT(SSL_NOT_OK - 3 == SSL_EAGAIN); MP_STATIC_ASSERT(SSL_ERROR_CONN_LOST - 18 == SSL_ERROR_NOT_SUPPORTED); diff --git a/extmod/modtls_mbedtls.c b/extmod/modtls_mbedtls.c index 6c34805da42cb..71a14adcff12d 100644 --- a/extmod/modtls_mbedtls.c +++ b/extmod/modtls_mbedtls.c @@ -147,7 +147,7 @@ static const unsigned char *asn1_get_data(mp_obj_t obj, size_t *out_len) { return (const unsigned char *)str; } -static NORETURN void mbedtls_raise_error(int err) { +static MP_NORETURN void mbedtls_raise_error(int err) { // Handle special cases. if (err == MBEDTLS_ERR_SSL_ALLOC_FAILED) { mp_raise_OSError(MP_ENOMEM); diff --git a/extmod/moductypes.c b/extmod/moductypes.c index 54abce79e06fe..eb72f441bbbce 100644 --- a/extmod/moductypes.c +++ b/extmod/moductypes.c @@ -89,7 +89,7 @@ typedef struct _mp_obj_uctypes_struct_t { uint32_t flags; } mp_obj_uctypes_struct_t; -static NORETURN void syntax_error(void) { +static MP_NORETURN void syntax_error(void) { mp_raise_TypeError(MP_ERROR_TEXT("syntax error in uctypes descriptor")); } diff --git a/mpy-cross/mpconfigport.h b/mpy-cross/mpconfigport.h index d3805f0307356..94a598c9954b8 100644 --- a/mpy-cross/mpconfigport.h +++ b/mpy-cross/mpconfigport.h @@ -137,7 +137,7 @@ typedef long mp_off_t; #ifdef _MSC_VER #define MP_ENDIANNESS_LITTLE (1) -#define NORETURN __declspec(noreturn) +#define MP_NORETURN __declspec(noreturn) #define MP_NOINLINE __declspec(noinline) #define MP_ALWAYSINLINE __forceinline #define MP_LIKELY(x) (x) diff --git a/ports/alif/main.c b/ports/alif/main.c index fd35604cbc234..47836113577e8 100644 --- a/ports/alif/main.c +++ b/ports/alif/main.c @@ -56,7 +56,7 @@ extern uint8_t __StackTop, __StackLimit; extern uint8_t __GcHeapStart, __GcHeapEnd; -NORETURN void panic(const char *msg) { +MP_NORETURN void panic(const char *msg) { mp_hal_stdout_tx_strn("\nFATAL ERROR:\n", 14); mp_hal_stdout_tx_strn(msg, strlen(msg)); for (;;) { diff --git a/ports/alif/modmachine.c b/ports/alif/modmachine.c index 9868abbeeaf35..71b5be5193d1c 100644 --- a/ports/alif/modmachine.c +++ b/ports/alif/modmachine.c @@ -47,11 +47,11 @@ static mp_obj_t mp_machine_unique_id(void) { return mp_obj_new_bytes(id, sizeof(id)); } -NORETURN static void mp_machine_reset(void) { +MP_NORETURN static void mp_machine_reset(void) { se_services_reset_soc(); } -NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { +MP_NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { __disable_irq(); MICROPY_BOARD_ENTER_BOOTLOADER(n_args, args); @@ -112,7 +112,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { #endif } -NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { +MP_NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { #if MICROPY_HW_ENABLE_USBDEV mp_machine_enable_usb(false); #endif diff --git a/ports/cc3200/misc/mperror.c b/ports/cc3200/misc/mperror.c index 6d6c0ff0bae8a..69f0a25371c13 100644 --- a/ports/cc3200/misc/mperror.c +++ b/ports/cc3200/misc/mperror.c @@ -159,7 +159,7 @@ void mperror_heartbeat_signal (void) { } } -void NORETURN __fatal_error(const char *msg) { +void MP_NORETURN __fatal_error(const char *msg) { #ifdef DEBUG if (msg != NULL) { // wait for 20ms diff --git a/ports/cc3200/misc/mperror.h b/ports/cc3200/misc/mperror.h index 1c3eb62697009..30effbbf91162 100644 --- a/ports/cc3200/misc/mperror.h +++ b/ports/cc3200/misc/mperror.h @@ -27,7 +27,7 @@ #ifndef MICROPY_INCLUDED_CC3200_MISC_MPERROR_H #define MICROPY_INCLUDED_CC3200_MISC_MPERROR_H -extern void NORETURN __fatal_error(const char *msg); +extern void MP_NORETURN __fatal_error(const char *msg); void mperror_init0 (void); void mperror_bootloader_check_reset_cause (void); diff --git a/ports/cc3200/mods/modmachine.c b/ports/cc3200/mods/modmachine.c index f7184df442736..8986635ac6577 100644 --- a/ports/cc3200/mods/modmachine.c +++ b/ports/cc3200/mods/modmachine.c @@ -93,7 +93,7 @@ extern OsiTaskHandle xSimpleLinkSpawnTaskHndl; /******************************************************************************/ // MicroPython bindings; -NORETURN static void mp_machine_reset(void) { +MP_NORETURN static void mp_machine_reset(void) { // disable wlan wlan_stop(SL_STOP_TIMEOUT_LONG); // reset the cpu and it's peripherals @@ -161,7 +161,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { pyb_sleep_sleep(); } -NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { +MP_NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { pyb_sleep_deepsleep(); for (;;) { } diff --git a/ports/cc3200/mods/pybsleep.c b/ports/cc3200/mods/pybsleep.c index 9d04dd411ddcd..291860de8d676 100644 --- a/ports/cc3200/mods/pybsleep.c +++ b/ports/cc3200/mods/pybsleep.c @@ -136,7 +136,7 @@ static MP_DEFINE_CONST_OBJ_TYPE( ******************************************************************************/ static pyb_sleep_obj_t *pyb_sleep_find (mp_obj_t obj); static void pyb_sleep_flash_powerdown (void); -static NORETURN void pyb_sleep_suspend_enter (void); +static MP_NORETURN void pyb_sleep_suspend_enter (void); void pyb_sleep_suspend_exit (void); static void pyb_sleep_obj_wakeup (void); static void PRCMInterruptHandler (void); @@ -360,7 +360,7 @@ static void pyb_sleep_flash_powerdown (void) { MAP_SPICSDisable(SSPI_BASE); } -static NORETURN void pyb_sleep_suspend_enter (void) { +static MP_NORETURN void pyb_sleep_suspend_enter (void) { // enable full RAM retention MAP_PRCMSRAMRetentionEnable(PRCM_SRAM_COL_1 | PRCM_SRAM_COL_2 | PRCM_SRAM_COL_3 | PRCM_SRAM_COL_4, PRCM_SRAM_LPDS_RET); diff --git a/ports/esp32/modmachine.c b/ports/esp32/modmachine.c index 0c1b94d02d940..0d7ea44c66960 100644 --- a/ports/esp32/modmachine.c +++ b/ports/esp32/modmachine.c @@ -178,7 +178,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { machine_sleep_helper(MACHINE_WAKE_SLEEP, n_args, args); }; -NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { +MP_NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { machine_sleep_helper(MACHINE_WAKE_DEEPSLEEP, n_args, args); mp_machine_reset(); }; @@ -221,7 +221,7 @@ static mp_int_t mp_machine_reset_cause(void) { #include "esp32s3/rom/usb/chip_usb_dw_wrapper.h" #endif -NORETURN static void machine_bootloader_rtc(void) { +MP_NORETURN static void machine_bootloader_rtc(void) { #if CONFIG_IDF_TARGET_ESP32S3 && MICROPY_HW_USB_CDC usb_usj_mode(); usb_dc_prepare_persist(); @@ -233,7 +233,7 @@ NORETURN static void machine_bootloader_rtc(void) { #endif #ifdef MICROPY_BOARD_ENTER_BOOTLOADER -NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { +MP_NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { MICROPY_BOARD_ENTER_BOOTLOADER(n_args, args); for (;;) { } @@ -254,7 +254,7 @@ static mp_obj_t machine_wake_reason(size_t n_args, const mp_obj_t *pos_args, mp_ } static MP_DEFINE_CONST_FUN_OBJ_KW(machine_wake_reason_obj, 0, machine_wake_reason); -NORETURN static void mp_machine_reset(void) { +MP_NORETURN static void mp_machine_reset(void) { esp_restart(); } diff --git a/ports/esp32/modnetwork.h b/ports/esp32/modnetwork.h index 387f961976d60..308a515c6e249 100644 --- a/ports/esp32/modnetwork.h +++ b/ports/esp32/modnetwork.h @@ -61,7 +61,7 @@ MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(esp_network_phy_mode_obj); mp_obj_t esp_ifname(esp_netif_t *netif); -NORETURN void esp_exceptions_helper(esp_err_t e); +MP_NORETURN void esp_exceptions_helper(esp_err_t e); static inline void esp_exceptions(esp_err_t e) { if (e != ESP_OK) { diff --git a/ports/esp32/network_common.c b/ports/esp32/network_common.c index fce8a0304c8be..e07d7a993f25f 100644 --- a/ports/esp32/network_common.c +++ b/ports/esp32/network_common.c @@ -45,7 +45,7 @@ #include "lwip/sockets.h" #include "lwip/dns.h" -NORETURN void esp_exceptions_helper(esp_err_t e) { +MP_NORETURN void esp_exceptions_helper(esp_err_t e) { switch (e) { case ESP_ERR_WIFI_NOT_INIT: mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Wifi Not Initialized")); diff --git a/ports/esp8266/esp_init_data.c b/ports/esp8266/esp_init_data.c index c369ed58f515f..a3dd6ffed1ea4 100644 --- a/ports/esp8266/esp_init_data.c +++ b/ports/esp8266/esp_init_data.c @@ -30,7 +30,7 @@ #include "user_interface.h" #include "extmod/misc.h" -NORETURN void call_user_start(void); +MP_NORETURN void call_user_start(void); void ets_printf(const char *fmt, ...); extern char flashchip; diff --git a/ports/esp8266/modmachine.c b/ports/esp8266/modmachine.c index 23ccf8cebfb0d..6ac17da457622 100644 --- a/ports/esp8266/modmachine.c +++ b/ports/esp8266/modmachine.c @@ -71,7 +71,7 @@ static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { system_update_cpu_freq(freq); } -NORETURN static void mp_machine_reset(void) { +MP_NORETURN static void mp_machine_reset(void) { system_restart(); // we must not return @@ -114,7 +114,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { } } -NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { +MP_NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { // default to sleep forever uint32_t sleep_us = 0; diff --git a/ports/mimxrt/modmachine.c b/ports/mimxrt/modmachine.c index 204699bcc4263..ad078ff3ac6d2 100644 --- a/ports/mimxrt/modmachine.c +++ b/ports/mimxrt/modmachine.c @@ -87,7 +87,7 @@ static mp_obj_t mp_machine_unique_id(void) { return mp_obj_new_bytes(id, sizeof(id)); } -NORETURN static void mp_machine_reset(void) { +MP_NORETURN static void mp_machine_reset(void) { WDOG_TriggerSystemSoftwareReset(WDOG1); while (true) { ; @@ -131,7 +131,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { mp_raise_NotImplementedError(NULL); } -NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { +MP_NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { if (n_args != 0) { mp_int_t seconds = mp_obj_get_int(args[0]) / 1000; if (seconds > 0) { @@ -159,7 +159,7 @@ NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { } } -NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { +MP_NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { #if defined(MICROPY_BOARD_ENTER_BOOTLOADER) // If a board has a custom bootloader, call it first. MICROPY_BOARD_ENTER_BOOTLOADER(n_args, args); diff --git a/ports/minimal/main.c b/ports/minimal/main.c index 5f472c1afd657..b9e9034bf5172 100644 --- a/ports/minimal/main.c +++ b/ports/minimal/main.c @@ -87,7 +87,7 @@ void nlr_jump_fail(void *val) { } } -void NORETURN __fatal_error(const char *msg) { +void MP_NORETURN __fatal_error(const char *msg) { while (1) { ; } diff --git a/ports/nrf/main.c b/ports/nrf/main.c index 29550bd77a748..99ed39895eb5a 100644 --- a/ports/nrf/main.c +++ b/ports/nrf/main.c @@ -107,7 +107,7 @@ void do_str(const char *src, mp_parse_input_kind_t input_kind) { extern uint32_t _heap_start; extern uint32_t _heap_end; -void NORETURN _start(void) { +void MP_NORETURN _start(void) { // Hook for a board to run code at start up, for example check if a // bootloader should be entered instead of the main application. MICROPY_BOARD_STARTUP(); @@ -353,7 +353,7 @@ void HardFault_Handler(void) { #endif } -void NORETURN __fatal_error(const char *msg) { +void MP_NORETURN __fatal_error(const char *msg) { while (1) { ; } diff --git a/ports/nrf/modules/machine/modmachine.c b/ports/nrf/modules/machine/modmachine.c index de1d0e31246b4..aa1f5a8a4a0c4 100644 --- a/ports/nrf/modules/machine/modmachine.c +++ b/ports/nrf/modules/machine/modmachine.c @@ -181,11 +181,11 @@ static mp_obj_t mp_machine_unique_id(void) { } // Resets the board in a manner similar to pushing the external RESET button. -NORETURN static void mp_machine_reset(void) { +MP_NORETURN static void mp_machine_reset(void) { NVIC_SystemReset(); } -NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { +MP_NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { MICROPY_BOARD_ENTER_BOOTLOADER(n_args, args); for (;;) { } @@ -199,7 +199,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { __WFE(); } -NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { +MP_NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { mp_machine_reset(); } diff --git a/ports/nrf/mphalport.c b/ports/nrf/mphalport.c index 9bb51deb12153..3a3d3ad7a6863 100644 --- a/ports/nrf/mphalport.c +++ b/ports/nrf/mphalport.c @@ -202,7 +202,7 @@ const byte mp_hal_status_to_errno_table[4] = { [HAL_TIMEOUT] = MP_ETIMEDOUT, }; -NORETURN void mp_hal_raise(HAL_StatusTypeDef status) { +MP_NORETURN void mp_hal_raise(HAL_StatusTypeDef status) { mp_raise_OSError(mp_hal_status_to_errno_table[status]); } diff --git a/ports/nrf/mphalport.h b/ports/nrf/mphalport.h index a0bca58a625ba..3a89636aec6bc 100644 --- a/ports/nrf/mphalport.h +++ b/ports/nrf/mphalport.h @@ -47,7 +47,7 @@ extern const unsigned char mp_hal_status_to_errno_table[4]; extern ringbuf_t stdin_ringbuf; -NORETURN void mp_hal_raise(HAL_StatusTypeDef status); +MP_NORETURN void mp_hal_raise(HAL_StatusTypeDef status); void mp_hal_set_interrupt_char(int c); // -1 to disable int mp_hal_stdin_rx_chr(void); diff --git a/ports/pic16bit/main.c b/ports/pic16bit/main.c index b1fe7321f0d46..437f91ca7c75c 100644 --- a/ports/pic16bit/main.c +++ b/ports/pic16bit/main.c @@ -121,7 +121,7 @@ void nlr_jump_fail(void *val) { } } -void NORETURN __fatal_error(const char *msg) { +void MP_NORETURN __fatal_error(const char *msg) { while (1) { ; } diff --git a/ports/powerpc/main.c b/ports/powerpc/main.c index 4b668c861c96b..bbd2082b42b17 100644 --- a/ports/powerpc/main.c +++ b/ports/powerpc/main.c @@ -130,7 +130,7 @@ void nlr_jump_fail(void *val) { } } -void NORETURN __fatal_error(const char *msg) { +void MP_NORETURN __fatal_error(const char *msg) { while (1) { ; } diff --git a/ports/qemu/mcu/arm/errorhandler.c b/ports/qemu/mcu/arm/errorhandler.c index a0e7ef930f0b8..a647c0e153606 100644 --- a/ports/qemu/mcu/arm/errorhandler.c +++ b/ports/qemu/mcu/arm/errorhandler.c @@ -63,7 +63,7 @@ static const char *EXCEPTION_NAMES_TABLE[] = { // R0-R15, PSR, Kind uintptr_t registers_copy[18] = { 0 }; -__attribute__((naked)) NORETURN void exception_handler(uintptr_t kind) { +__attribute__((naked)) MP_NORETURN void exception_handler(uintptr_t kind) { // Save registers __asm volatile ( "ldr r1, =registers_copy \n" @@ -137,39 +137,39 @@ __attribute__((naked)) NORETURN void exception_handler(uintptr_t kind) { for (;;) {} } -__attribute__((naked)) NORETURN void NMI_Handler(void) { +__attribute__((naked)) MP_NORETURN void NMI_Handler(void) { exception_handler(NMI); } -__attribute__((naked)) NORETURN void HardFault_Handler(void) { +__attribute__((naked)) MP_NORETURN void HardFault_Handler(void) { exception_handler(HARD_FAULT); } -__attribute__((naked)) NORETURN void MemManage_Handler(void) { +__attribute__((naked)) MP_NORETURN void MemManage_Handler(void) { exception_handler(MEM_MANAGE); } -__attribute__((naked)) NORETURN void BusFault_Handler(void) { +__attribute__((naked)) MP_NORETURN void BusFault_Handler(void) { exception_handler(BUS_FAULT); } -__attribute__((naked)) NORETURN void UsageFault_Handler(void) { +__attribute__((naked)) MP_NORETURN void UsageFault_Handler(void) { exception_handler(USAGE_FAULT); } -__attribute__((naked)) NORETURN void SVC_Handler(void) { +__attribute__((naked)) MP_NORETURN void SVC_Handler(void) { exception_handler(SV_CALL); } -__attribute__((naked)) NORETURN void DebugMon_Handler(void) { +__attribute__((naked)) MP_NORETURN void DebugMon_Handler(void) { exception_handler(DEBUG_MONITOR); } -__attribute__((naked)) NORETURN void PendSV_Handler(void) { +__attribute__((naked)) MP_NORETURN void PendSV_Handler(void) { exception_handler(PENDING_SV); } -__attribute__((naked)) NORETURN void SysTick_Handler(void) { +__attribute__((naked)) MP_NORETURN void SysTick_Handler(void) { exception_handler(SYSTEM_TICK); } diff --git a/ports/qemu/mcu/arm/startup.c b/ports/qemu/mcu/arm/startup.c index dbb75e3392287..a7e9efb9fba0f 100644 --- a/ports/qemu/mcu/arm/startup.c +++ b/ports/qemu/mcu/arm/startup.c @@ -52,7 +52,7 @@ __attribute__((naked)) void Reset_Handler(void) { _start(); } -NORETURN void Default_Handler(void) { +MP_NORETURN void Default_Handler(void) { for (;;) { } } diff --git a/ports/renesas-ra/main.c b/ports/renesas-ra/main.c index febb7b6d7aaa3..cfc4611ecb5cd 100644 --- a/ports/renesas-ra/main.c +++ b/ports/renesas-ra/main.c @@ -88,7 +88,7 @@ static machine_uart_obj_t machine_uart_repl_obj; static uint8_t machine_uart_repl_rxbuf[MICROPY_HW_UART_REPL_RXBUF]; #endif -void NORETURN __fatal_error(const char *msg) { +void MP_NORETURN __fatal_error(const char *msg) { for (volatile uint delay = 0; delay < 1000000; delay++) { } led_state(1, 1); diff --git a/ports/renesas-ra/modmachine.c b/ports/renesas-ra/modmachine.c index dc38d809dd701..c23ce7e4695da 100644 --- a/ports/renesas-ra/modmachine.c +++ b/ports/renesas-ra/modmachine.c @@ -184,12 +184,12 @@ static mp_obj_t mp_machine_unique_id(void) { } // Resets the pyboard in a manner similar to pushing the external RESET button. -NORETURN static void mp_machine_reset(void) { +MP_NORETURN static void mp_machine_reset(void) { powerctrl_mcu_reset(); } // Activate the bootloader without BOOT* pins. -NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { +MP_NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { #if MICROPY_HW_ENABLE_STORAGE storage_flush(); #endif @@ -232,7 +232,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { powerctrl_enter_stop_mode(); } -NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { +MP_NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { if (n_args != 0) { mp_obj_t args2[2] = {MP_OBJ_NULL, args[0]}; machine_rtc_wakeup(2, args2); diff --git a/ports/renesas-ra/mphalport.c b/ports/renesas-ra/mphalport.c index 1c62c23dd70be..b2587af06de2c 100644 --- a/ports/renesas-ra/mphalport.c +++ b/ports/renesas-ra/mphalport.c @@ -61,7 +61,7 @@ const byte mp_hal_status_to_errno_table[4] = { [HAL_TIMEOUT] = MP_ETIMEDOUT, }; -NORETURN void mp_hal_raise(HAL_StatusTypeDef status) { +MP_NORETURN void mp_hal_raise(HAL_StatusTypeDef status) { mp_raise_OSError(mp_hal_status_to_errno_table[status]); } diff --git a/ports/renesas-ra/mphalport.h b/ports/renesas-ra/mphalport.h index 0819abeaf617e..1dab67f9a76db 100644 --- a/ports/renesas-ra/mphalport.h +++ b/ports/renesas-ra/mphalport.h @@ -49,7 +49,7 @@ static inline int mp_hal_status_to_neg_errno(HAL_StatusTypeDef status) { return -mp_hal_status_to_errno_table[status]; } -NORETURN void mp_hal_raise(HAL_StatusTypeDef status); +MP_NORETURN void mp_hal_raise(HAL_StatusTypeDef status); void mp_hal_set_interrupt_char(int c); // -1 to disable static inline void mp_hal_wake_main_task_from_isr(void) { diff --git a/ports/renesas-ra/powerctrl.c b/ports/renesas-ra/powerctrl.c index 548d31679adaa..04c39b35110b2 100644 --- a/ports/renesas-ra/powerctrl.c +++ b/ports/renesas-ra/powerctrl.c @@ -174,7 +174,7 @@ const lpm_instance_t g_lpm_standby = { #endif -NORETURN void powerctrl_mcu_reset(void) { +MP_NORETURN void powerctrl_mcu_reset(void) { #if BSP_TZ_SECURE_BUILD R_BSP_NonSecureEnter(); #else @@ -185,7 +185,7 @@ NORETURN void powerctrl_mcu_reset(void) { } } -NORETURN void powerctrl_enter_bootloader(uint32_t r0, uint32_t bl_addr) { +MP_NORETURN void powerctrl_enter_bootloader(uint32_t r0, uint32_t bl_addr) { while (1) { ; } @@ -245,7 +245,7 @@ void powerctrl_enter_stop_mode(void) { enable_irq(irq_state); } -NORETURN void powerctrl_enter_standby_mode(void) { +MP_NORETURN void powerctrl_enter_standby_mode(void) { rtc_init_finalise(); #if defined(MICROPY_BOARD_ENTER_STANDBY) diff --git a/ports/renesas-ra/powerctrl.h b/ports/renesas-ra/powerctrl.h index 34c40ea1adc32..37932b1002bee 100644 --- a/ports/renesas-ra/powerctrl.h +++ b/ports/renesas-ra/powerctrl.h @@ -32,11 +32,11 @@ void SystemClock_Config(void); -NORETURN void powerctrl_mcu_reset(void); -NORETURN void powerctrl_enter_bootloader(uint32_t r0, uint32_t bl_addr); +MP_NORETURN void powerctrl_mcu_reset(void); +MP_NORETURN void powerctrl_enter_bootloader(uint32_t r0, uint32_t bl_addr); void powerctrl_check_enter_bootloader(void); void powerctrl_enter_stop_mode(void); -NORETURN void powerctrl_enter_standby_mode(void); +MP_NORETURN void powerctrl_enter_standby_mode(void); #endif // MICROPY_INCLUDED_RA_POWERCTRL_H diff --git a/ports/renesas-ra/uart.c b/ports/renesas-ra/uart.c index c319b4c0a8dca..4800e0ea03f95 100644 --- a/ports/renesas-ra/uart.c +++ b/ports/renesas-ra/uart.c @@ -42,7 +42,7 @@ typedef int (*KEYEX_CB)(uint32_t d); -extern void NORETURN __fatal_error(const char *msg); +extern void MP_NORETURN __fatal_error(const char *msg); #if MICROPY_KBD_EXCEPTION extern int mp_interrupt_char; static KEYEX_CB keyex_cb[MICROPY_HW_MAX_UART] = {(KEYEX_CB)NULL}; diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 3625d404cdc7b..1f1b0e2f59b1e 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -65,7 +65,7 @@ static mp_obj_t mp_machine_unique_id(void) { return mp_obj_new_bytes(id.id, sizeof(id.id)); } -NORETURN static void mp_machine_reset(void) { +MP_NORETURN static void mp_machine_reset(void) { watchdog_reboot(0, SRAM_END, 0); for (;;) { __wfi(); @@ -82,7 +82,7 @@ static mp_int_t mp_machine_reset_cause(void) { return reset_cause; } -NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { +MP_NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { MICROPY_BOARD_ENTER_BOOTLOADER(n_args, args); rosc_hw->ctrl = ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB; reset_usb_boot(0, 0); @@ -338,7 +338,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { #endif } -NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { +MP_NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { mp_machine_lightsleep(n_args, args); mp_machine_reset(); } diff --git a/ports/samd/modmachine.c b/ports/samd/modmachine.c index 82e7b7c062045..c20ce8d641d39 100644 --- a/ports/samd/modmachine.c +++ b/ports/samd/modmachine.c @@ -65,7 +65,7 @@ extern bool EIC_occured; extern uint32_t _dbl_tap_addr; -NORETURN static void mp_machine_reset(void) { +MP_NORETURN static void mp_machine_reset(void) { *DBL_TAP_ADDR = DBL_TAP_MAGIC_RESET; #ifdef DBL_TAP_ADDR_ALT *DBL_TAP_ADDR_ALT = DBL_TAP_MAGIC_RESET; @@ -73,7 +73,7 @@ NORETURN static void mp_machine_reset(void) { NVIC_SystemReset(); } -NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { +MP_NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { *DBL_TAP_ADDR = DBL_TAP_MAGIC_LOADER; #ifdef DBL_TAP_ADDR_ALT *DBL_TAP_ADDR_ALT = DBL_TAP_MAGIC_LOADER; @@ -164,7 +164,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { set_cpu_freq(freq); } -NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { +MP_NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) { mp_machine_lightsleep(n_args, args); mp_machine_reset(); } diff --git a/ports/stm32/boardctrl.c b/ports/stm32/boardctrl.c index 8f0d066f372b4..ea95c8d2d59dd 100644 --- a/ports/stm32/boardctrl.c +++ b/ports/stm32/boardctrl.c @@ -35,7 +35,7 @@ #include "led.h" #include "usrsw.h" -NORETURN void boardctrl_fatal_error(const char *msg) { +MP_NORETURN void boardctrl_fatal_error(const char *msg) { for (volatile uint delay = 0; delay < 10000000; delay++) { } led_state(1, 1); diff --git a/ports/stm32/boardctrl.h b/ports/stm32/boardctrl.h index 8f4ce30eff80a..1a03925ef46da 100644 --- a/ports/stm32/boardctrl.h +++ b/ports/stm32/boardctrl.h @@ -114,7 +114,7 @@ typedef struct _boardctrl_state_t { bool log_soft_reset; } boardctrl_state_t; -NORETURN void boardctrl_fatal_error(const char *msg); +MP_NORETURN void boardctrl_fatal_error(const char *msg); void boardctrl_maybe_enter_mboot(size_t n_args, const void *args); void boardctrl_before_soft_reset_loop(boardctrl_state_t *state); void boardctrl_top_soft_reset_loop(boardctrl_state_t *state); diff --git a/ports/stm32/mboot/main.c b/ports/stm32/mboot/main.c index 01f8892a514c8..ff44dac630aae 100644 --- a/ports/stm32/mboot/main.c +++ b/ports/stm32/mboot/main.c @@ -176,7 +176,7 @@ void HAL_Delay(uint32_t ms) { mp_hal_delay_ms(ms); } -NORETURN static void __fatal_error(const char *msg) { +MP_NORETURN static void __fatal_error(const char *msg) { NVIC_SystemReset(); for (;;) { } @@ -1443,7 +1443,7 @@ static int pyb_usbdd_shutdown(void) { /******************************************************************************/ // main -NORETURN static __attribute__((naked)) void branch_to_application(uint32_t r0, uint32_t bl_addr) { +MP_NORETURN static __attribute__((naked)) void branch_to_application(uint32_t r0, uint32_t bl_addr) { __asm volatile ( "ldr r2, [r1, #0]\n" // get address of stack pointer "msr msp, r2\n" // set stack pointer diff --git a/ports/stm32/mboot/mboot.h b/ports/stm32/mboot/mboot.h index 1fabff0080b1c..bc1320c5cdb36 100644 --- a/ports/stm32/mboot/mboot.h +++ b/ports/stm32/mboot/mboot.h @@ -36,7 +36,7 @@ #define ELEM_DATA_START (&_estack[0]) #define ELEM_DATA_MAX (&_estack[ELEM_DATA_SIZE]) -#define NORETURN __attribute__((noreturn)) +#define MP_NORETURN __attribute__((noreturn)) #define MP_ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) // The default UI code in ui.c only works if there is at least one LED configured. diff --git a/ports/stm32/modmachine.c b/ports/stm32/modmachine.c index 620ae468cb9b3..f3bdf1bc50278 100644 --- a/ports/stm32/modmachine.c +++ b/ports/stm32/modmachine.c @@ -291,12 +291,12 @@ static mp_obj_t mp_machine_unique_id(void) { } // Resets the pyboard in a manner similar to pushing the external RESET button. -NORETURN static void mp_machine_reset(void) { +MP_NORETURN static void mp_machine_reset(void) { powerctrl_mcu_reset(); } // Activate the bootloader without BOOT* pins. -NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { +MP_NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) { #if MICROPY_HW_ENABLE_USB pyb_usb_dev_deinit(); #endif diff --git a/ports/stm32/mphalport.c b/ports/stm32/mphalport.c index dfd50cebd3dee..b4b2267fa02b1 100644 --- a/ports/stm32/mphalport.c +++ b/ports/stm32/mphalport.c @@ -20,7 +20,7 @@ const byte mp_hal_status_to_errno_table[4] = { uint8_t mp_hal_unique_id_address[12]; #endif -NORETURN void mp_hal_raise(HAL_StatusTypeDef status) { +MP_NORETURN void mp_hal_raise(HAL_StatusTypeDef status) { mp_raise_OSError(mp_hal_status_to_errno_table[status]); } diff --git a/ports/stm32/mphalport.h b/ports/stm32/mphalport.h index e520bc54ae6c0..03b0f8e7722ae 100644 --- a/ports/stm32/mphalport.h +++ b/ports/stm32/mphalport.h @@ -37,7 +37,7 @@ static inline int mp_hal_status_to_neg_errno(HAL_StatusTypeDef status) { return -mp_hal_status_to_errno_table[status]; } -NORETURN void mp_hal_raise(HAL_StatusTypeDef status); +MP_NORETURN void mp_hal_raise(HAL_StatusTypeDef status); void mp_hal_set_interrupt_char(int c); // -1 to disable // Atomic section helpers. diff --git a/ports/stm32/powerctrl.c b/ports/stm32/powerctrl.c index eea009e2d782b..e3e2fcdd44eb5 100644 --- a/ports/stm32/powerctrl.c +++ b/ports/stm32/powerctrl.c @@ -115,7 +115,7 @@ static inline void powerctrl_disable_hsi_if_unused(void) { #endif } -NORETURN void powerctrl_mcu_reset(void) { +MP_NORETURN void powerctrl_mcu_reset(void) { #if MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET *BL_STATE_PTR = BL_STATE_INVALID; #if __DCACHE_PRESENT == 1 @@ -125,7 +125,7 @@ NORETURN void powerctrl_mcu_reset(void) { NVIC_SystemReset(); } -NORETURN static __attribute__((naked)) void branch_to_bootloader(uint32_t r0, uint32_t bl_addr) { +MP_NORETURN static __attribute__((naked)) void branch_to_bootloader(uint32_t r0, uint32_t bl_addr) { __asm volatile ( "ldr r2, [r1, #0]\n" // get address of stack pointer "msr msp, r2\n" // get stack pointer @@ -135,7 +135,7 @@ NORETURN static __attribute__((naked)) void branch_to_bootloader(uint32_t r0, ui MP_UNREACHABLE; } -NORETURN void powerctrl_enter_bootloader(uint32_t r0, uint32_t bl_addr) { +MP_NORETURN void powerctrl_enter_bootloader(uint32_t r0, uint32_t bl_addr) { #if MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET // Enter the bootloader via a reset, so everything is reset (including WDT). @@ -1016,7 +1016,7 @@ void powerctrl_enter_stop_mode(void) { enable_irq(irq_state); } -NORETURN void powerctrl_enter_standby_mode(void) { +MP_NORETURN void powerctrl_enter_standby_mode(void) { rtc_init_finalise(); #if defined(MICROPY_BOARD_ENTER_STANDBY) diff --git a/ports/stm32/powerctrl.h b/ports/stm32/powerctrl.h index 5b9240561182f..05a70e52c6aaa 100644 --- a/ports/stm32/powerctrl.h +++ b/ports/stm32/powerctrl.h @@ -39,14 +39,14 @@ static inline void stm32_system_init(void) { void SystemClock_Config(void); -NORETURN void powerctrl_mcu_reset(void); -NORETURN void powerctrl_enter_bootloader(uint32_t r0, uint32_t bl_addr); +MP_NORETURN void powerctrl_mcu_reset(void); +MP_NORETURN void powerctrl_enter_bootloader(uint32_t r0, uint32_t bl_addr); void powerctrl_check_enter_bootloader(void); void powerctrl_config_systick(void); int powerctrl_rcc_clock_config_pll(RCC_ClkInitTypeDef *rcc_init, uint32_t sysclk_mhz, bool need_pllsai); int powerctrl_set_sysclk(uint32_t sysclk, uint32_t ahb, uint32_t apb1, uint32_t apb2); void powerctrl_enter_stop_mode(void); -NORETURN void powerctrl_enter_standby_mode(void); +MP_NORETURN void powerctrl_enter_standby_mode(void); #endif // MICROPY_INCLUDED_STM32_POWERCTRL_H diff --git a/ports/webassembly/main.c b/ports/webassembly/main.c index c542f0cd72efe..770dfbe0ca5c7 100644 --- a/ports/webassembly/main.c +++ b/ports/webassembly/main.c @@ -233,7 +233,7 @@ void nlr_jump_fail(void *val) { } } -void NORETURN __fatal_error(const char *msg) { +void MP_NORETURN __fatal_error(const char *msg) { while (1) { ; } diff --git a/ports/windows/mpconfigport.h b/ports/windows/mpconfigport.h index fabc9072d6c70..6e32d5fe5ebdf 100644 --- a/ports/windows/mpconfigport.h +++ b/ports/windows/mpconfigport.h @@ -242,7 +242,7 @@ typedef long mp_off_t; // CL specific overrides from mpconfig -#define NORETURN __declspec(noreturn) +#define MP_NORETURN __declspec(noreturn) #define MP_WEAK #define MP_NOINLINE __declspec(noinline) #define MP_ALWAYSINLINE __forceinline diff --git a/ports/zephyr/main.c b/ports/zephyr/main.c index d4498c1079c1d..45af7b0c72c40 100644 --- a/ports/zephyr/main.c +++ b/ports/zephyr/main.c @@ -219,7 +219,7 @@ mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open); #endif -NORETURN void nlr_jump_fail(void *val) { +MP_NORETURN void nlr_jump_fail(void *val) { while (1) { ; } diff --git a/py/argcheck.c b/py/argcheck.c index 35b116ec0d414..298c19bcfdbc3 100644 --- a/py/argcheck.c +++ b/py/argcheck.c @@ -137,12 +137,12 @@ void mp_arg_parse_all_kw_array(size_t n_pos, size_t n_kw, const mp_obj_t *args, mp_arg_parse_all(n_pos, args, &kw_args, n_allowed, allowed, out_vals); } -NORETURN void mp_arg_error_terse_mismatch(void) { +MP_NORETURN void mp_arg_error_terse_mismatch(void) { mp_raise_TypeError(MP_ERROR_TEXT("argument num/types mismatch")); } #if MICROPY_CPYTHON_COMPAT -NORETURN void mp_arg_error_unimpl_kw(void) { +MP_NORETURN void mp_arg_error_unimpl_kw(void) { mp_raise_NotImplementedError(MP_ERROR_TEXT("keyword argument(s) not implemented - use normal args instead")); } #endif diff --git a/py/bc.c b/py/bc.c index 899dbd6a0727d..cea31c93bd8a0 100644 --- a/py/bc.c +++ b/py/bc.c @@ -88,7 +88,7 @@ const byte *mp_decode_uint_skip(const byte *ptr) { return ptr; } -static NORETURN void fun_pos_args_mismatch(mp_obj_fun_bc_t *f, size_t expected, size_t given) { +static MP_NORETURN void fun_pos_args_mismatch(mp_obj_fun_bc_t *f, size_t expected, size_t given) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE // generic message, used also for other argument issues (void)f; diff --git a/py/dynruntime.h b/py/dynruntime.h index c93111bbd4d6f..0e438da4b7032 100644 --- a/py/dynruntime.h +++ b/py/dynruntime.h @@ -70,7 +70,7 @@ #define m_realloc(ptr, new_num_bytes) (m_realloc_dyn((ptr), (new_num_bytes))) #define m_realloc_maybe(ptr, new_num_bytes, allow_move) (m_realloc_maybe_dyn((ptr), (new_num_bytes), (allow_move))) -static NORETURN inline void m_malloc_fail_dyn(size_t num_bytes) { +static MP_NORETURN inline void m_malloc_fail_dyn(size_t num_bytes) { mp_fun_table.raise_msg( mp_fun_table.load_global(MP_QSTR_MemoryError), "memory allocation failed"); @@ -295,7 +295,7 @@ static inline mp_obj_t mp_obj_new_exception_arg1_dyn(const mp_obj_type_t *exc_ty return mp_call_function_n_kw(MP_OBJ_FROM_PTR(exc_type), 1, 0, &args[0]); } -static NORETURN inline void mp_raise_dyn(mp_obj_t o) { +static MP_NORETURN inline void mp_raise_dyn(mp_obj_t o) { mp_fun_table.raise(o); for (;;) { } diff --git a/py/misc.h b/py/misc.h index e05fbe61a9d6d..49f2f87111157 100644 --- a/py/misc.h +++ b/py/misc.h @@ -105,7 +105,7 @@ void *m_realloc(void *ptr, size_t new_num_bytes); void *m_realloc_maybe(void *ptr, size_t new_num_bytes, bool allow_move); void m_free(void *ptr); #endif -NORETURN void m_malloc_fail(size_t num_bytes); +MP_NORETURN void m_malloc_fail(size_t num_bytes); #if MICROPY_TRACKED_ALLOC // These alloc/free functions track the pointers in a linked list so the GC does not reclaim diff --git a/py/modmath.c b/py/modmath.c index 4d51a28d0bbb0..b792d8581d062 100644 --- a/py/modmath.c +++ b/py/modmath.c @@ -37,7 +37,7 @@ #define MP_PI_4 MICROPY_FLOAT_CONST(0.78539816339744830962) #define MP_3_PI_4 MICROPY_FLOAT_CONST(2.35619449019234492885) -static NORETURN void math_error(void) { +static MP_NORETURN void math_error(void) { mp_raise_ValueError(MP_ERROR_TEXT("math domain error")); } diff --git a/py/mpconfig.h b/py/mpconfig.h index 9001b8983bf37..49a9fa35ccf02 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -2115,8 +2115,12 @@ typedef double mp_float_t; #endif // INT_FMT // Modifier for function which doesn't return -#ifndef NORETURN -#define NORETURN __attribute__((noreturn)) +#ifndef MP_NORETURN +#define MP_NORETURN __attribute__((noreturn)) +#endif + +#if !MICROPY_PREVIEW_VERSION_2 +#define NORETURN MP_NORETURN #endif // Modifier for weak functions diff --git a/py/nativeglue.h b/py/nativeglue.h index e96fd7b66a03f..2c7923c56df85 100644 --- a/py/nativeglue.h +++ b/py/nativeglue.h @@ -143,7 +143,7 @@ typedef struct _mp_fun_table_t { int (*printf_)(const mp_print_t *print, const char *fmt, ...); int (*vprintf_)(const mp_print_t *print, const char *fmt, va_list args); #if defined(__GNUC__) - NORETURN // Only certain compilers support no-return attributes in function pointer declarations + MP_NORETURN // Only certain compilers support no-return attributes in function pointer declarations #endif void (*raise_msg)(const mp_obj_type_t *exc_type, mp_rom_error_text_t msg); const mp_obj_type_t *(*obj_get_type)(mp_const_obj_t o_in); diff --git a/py/nlr.c b/py/nlr.c index 7ab0c0955a294..de2a38ceff3e9 100644 --- a/py/nlr.c +++ b/py/nlr.c @@ -81,7 +81,7 @@ void nlr_call_jump_callbacks(nlr_buf_t *nlr) { } #if MICROPY_ENABLE_VM_ABORT -NORETURN void nlr_jump_abort(void) { +MP_NORETURN void nlr_jump_abort(void) { MP_STATE_THREAD(nlr_top) = MP_STATE_VM(nlr_abort); nlr_jump(NULL); } diff --git a/py/nlr.h b/py/nlr.h index ce30bc91d67ae..47447c5d174ff 100644 --- a/py/nlr.h +++ b/py/nlr.h @@ -177,18 +177,18 @@ unsigned int nlr_push(nlr_buf_t *); unsigned int nlr_push_tail(nlr_buf_t *top); void nlr_pop(void); -NORETURN void nlr_jump(void *val); +MP_NORETURN void nlr_jump(void *val); #if MICROPY_ENABLE_VM_ABORT #define nlr_set_abort(buf) MP_STATE_VM(nlr_abort) = buf #define nlr_get_abort() MP_STATE_VM(nlr_abort) -NORETURN void nlr_jump_abort(void); +MP_NORETURN void nlr_jump_abort(void); #endif // This must be implemented by a port. It's called by nlr_jump // if no nlr buf has been pushed. It must not return, but rather // should bail out with a fatal error. -NORETURN void nlr_jump_fail(void *val); +MP_NORETURN void nlr_jump_fail(void *val); // use nlr_raise instead of nlr_jump so that debugging is easier #ifndef MICROPY_DEBUG_NLR diff --git a/py/nlraarch64.c b/py/nlraarch64.c index d6d87ebc50db8..3318004b5e037 100644 --- a/py/nlraarch64.c +++ b/py/nlraarch64.c @@ -56,7 +56,7 @@ __asm( #endif ); -NORETURN void nlr_jump(void *val) { +MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) MP_STATIC_ASSERT(offsetof(nlr_buf_t, regs) == 16); // asm assumes it diff --git a/py/nlrmips.c b/py/nlrmips.c index cba52b16a266a..5c55db7e268ec 100644 --- a/py/nlrmips.c +++ b/py/nlrmips.c @@ -57,7 +57,7 @@ __asm( ".end nlr_push \n" ); -NORETURN void nlr_jump(void *val) { +MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm( "move $4, %0 \n" diff --git a/py/nlrpowerpc.c b/py/nlrpowerpc.c index 8a69fe1eeca6b..cf140400e68f5 100644 --- a/py/nlrpowerpc.c +++ b/py/nlrpowerpc.c @@ -78,7 +78,7 @@ unsigned int nlr_push(nlr_buf_t *nlr) { return 0; } -NORETURN void nlr_jump(void *val) { +MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm__ volatile ( @@ -167,7 +167,7 @@ unsigned int nlr_push(nlr_buf_t *nlr) { return 0; } -NORETURN void nlr_jump(void *val) { +MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm__ volatile ( diff --git a/py/nlrrv32.c b/py/nlrrv32.c index 9a12ede400daa..565a8629db5f0 100644 --- a/py/nlrrv32.c +++ b/py/nlrrv32.c @@ -50,7 +50,7 @@ __attribute__((naked)) unsigned int nlr_push(nlr_buf_t *nlr) { ); } -NORETURN void nlr_jump(void *val) { +MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm volatile ( "add x10, x0, %0 \n" // Load nlr_buf address. diff --git a/py/nlrrv64.c b/py/nlrrv64.c index e7ba79797b857..b7d1467b8f6f6 100644 --- a/py/nlrrv64.c +++ b/py/nlrrv64.c @@ -50,7 +50,7 @@ __attribute__((naked)) unsigned int nlr_push(nlr_buf_t *nlr) { ); } -NORETURN void nlr_jump(void *val) { +MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm volatile ( "add x10, x0, %0 \n" // Load nlr_buf address. diff --git a/py/nlrthumb.c b/py/nlrthumb.c index e7b24f242bc52..8546308a3dedf 100644 --- a/py/nlrthumb.c +++ b/py/nlrthumb.c @@ -100,7 +100,7 @@ __attribute__((naked)) unsigned int nlr_push(nlr_buf_t *nlr) { #endif } -NORETURN void nlr_jump(void *val) { +MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm volatile ( diff --git a/py/nlrx64.c b/py/nlrx64.c index d1ad91ff7d718..51224729fc90d 100644 --- a/py/nlrx64.c +++ b/py/nlrx64.c @@ -100,7 +100,7 @@ unsigned int nlr_push(nlr_buf_t *nlr) { #endif } -NORETURN void nlr_jump(void *val) { +MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm volatile ( diff --git a/py/nlrx86.c b/py/nlrx86.c index 085e30d2034a1..26bf0dc6ccb85 100644 --- a/py/nlrx86.c +++ b/py/nlrx86.c @@ -78,7 +78,7 @@ unsigned int nlr_push(nlr_buf_t *nlr) { #endif } -NORETURN void nlr_jump(void *val) { +MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm volatile ( diff --git a/py/nlrxtensa.c b/py/nlrxtensa.c index ff7af6edeef98..2d1bf35e381a3 100644 --- a/py/nlrxtensa.c +++ b/py/nlrxtensa.c @@ -55,7 +55,7 @@ unsigned int nlr_push(nlr_buf_t *nlr) { return 0; // needed to silence compiler warning } -NORETURN void nlr_jump(void *val) { +MP_NORETURN void nlr_jump(void *val) { MP_NLR_JUMP_HEAD(val, top) __asm volatile ( diff --git a/py/objstr.c b/py/objstr.c index a160ab415c6cf..fda31d53185f9 100644 --- a/py/objstr.c +++ b/py/objstr.c @@ -40,7 +40,7 @@ static mp_obj_t str_modulo_format(mp_obj_t pattern, size_t n_args, const mp_obj_ #endif static mp_obj_t mp_obj_new_bytes_iterator(mp_obj_t str, mp_obj_iter_buf_t *iter_buf); -static NORETURN void bad_implicit_conversion(mp_obj_t self_in); +static MP_NORETURN void bad_implicit_conversion(mp_obj_t self_in); static mp_obj_t mp_obj_new_str_type_from_vstr(const mp_obj_type_t *type, vstr_t *vstr); @@ -1001,7 +1001,7 @@ static mp_obj_t arg_as_int(mp_obj_t arg) { #endif #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE -static NORETURN void terse_str_format_value_error(void) { +static MP_NORETURN void terse_str_format_value_error(void) { mp_raise_ValueError(MP_ERROR_TEXT("bad format string")); } #else @@ -2357,7 +2357,7 @@ bool mp_obj_str_equal(mp_obj_t s1, mp_obj_t s2) { } } -static NORETURN void bad_implicit_conversion(mp_obj_t self_in) { +static MP_NORETURN void bad_implicit_conversion(mp_obj_t self_in) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE mp_raise_TypeError(MP_ERROR_TEXT("can't convert to str implicitly")); #else diff --git a/py/parsenum.c b/py/parsenum.c index 3281eb4b8511a..a38ce563f9530 100644 --- a/py/parsenum.c +++ b/py/parsenum.c @@ -36,7 +36,7 @@ #include #endif -static NORETURN void raise_exc(mp_obj_t exc, mp_lexer_t *lex) { +static MP_NORETURN void raise_exc(mp_obj_t exc, mp_lexer_t *lex) { // if lex!=NULL then the parser called us and we need to convert the // exception's type from ValueError to SyntaxError and add traceback info if (lex != NULL) { diff --git a/py/runtime.c b/py/runtime.c index 58819819ad025..d6fea172f212b 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -1649,7 +1649,7 @@ mp_obj_t mp_parse_compile_execute(mp_lexer_t *lex, mp_parse_input_kind_t parse_i #endif // MICROPY_ENABLE_COMPILER -NORETURN void m_malloc_fail(size_t num_bytes) { +MP_NORETURN void m_malloc_fail(size_t num_bytes) { DEBUG_printf("memory allocation failed, allocating %u bytes\n", (uint)num_bytes); #if MICROPY_ENABLE_GC if (gc_is_locked()) { @@ -1662,25 +1662,25 @@ NORETURN void m_malloc_fail(size_t num_bytes) { #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NONE -NORETURN void mp_raise_type(const mp_obj_type_t *exc_type) { +MP_NORETURN void mp_raise_type(const mp_obj_type_t *exc_type) { nlr_raise(mp_obj_new_exception(exc_type)); } -NORETURN void mp_raise_ValueError_no_msg(void) { +MP_NORETURN void mp_raise_ValueError_no_msg(void) { mp_raise_type(&mp_type_ValueError); } -NORETURN void mp_raise_TypeError_no_msg(void) { +MP_NORETURN void mp_raise_TypeError_no_msg(void) { mp_raise_type(&mp_type_TypeError); } -NORETURN void mp_raise_NotImplementedError_no_msg(void) { +MP_NORETURN void mp_raise_NotImplementedError_no_msg(void) { mp_raise_type(&mp_type_NotImplementedError); } #else -NORETURN void mp_raise_msg(const mp_obj_type_t *exc_type, mp_rom_error_text_t msg) { +MP_NORETURN void mp_raise_msg(const mp_obj_type_t *exc_type, mp_rom_error_text_t msg) { if (msg == NULL) { nlr_raise(mp_obj_new_exception(exc_type)); } else { @@ -1688,7 +1688,7 @@ NORETURN void mp_raise_msg(const mp_obj_type_t *exc_type, mp_rom_error_text_t ms } } -NORETURN void mp_raise_msg_varg(const mp_obj_type_t *exc_type, mp_rom_error_text_t fmt, ...) { +MP_NORETURN void mp_raise_msg_varg(const mp_obj_type_t *exc_type, mp_rom_error_text_t fmt, ...) { va_list args; va_start(args, fmt); mp_obj_t exc = mp_obj_new_exception_msg_vlist(exc_type, fmt, args); @@ -1696,25 +1696,25 @@ NORETURN void mp_raise_msg_varg(const mp_obj_type_t *exc_type, mp_rom_error_text nlr_raise(exc); } -NORETURN void mp_raise_ValueError(mp_rom_error_text_t msg) { +MP_NORETURN void mp_raise_ValueError(mp_rom_error_text_t msg) { mp_raise_msg(&mp_type_ValueError, msg); } -NORETURN void mp_raise_TypeError(mp_rom_error_text_t msg) { +MP_NORETURN void mp_raise_TypeError(mp_rom_error_text_t msg) { mp_raise_msg(&mp_type_TypeError, msg); } -NORETURN void mp_raise_NotImplementedError(mp_rom_error_text_t msg) { +MP_NORETURN void mp_raise_NotImplementedError(mp_rom_error_text_t msg) { mp_raise_msg(&mp_type_NotImplementedError, msg); } #endif -NORETURN void mp_raise_type_arg(const mp_obj_type_t *exc_type, mp_obj_t arg) { +MP_NORETURN void mp_raise_type_arg(const mp_obj_type_t *exc_type, mp_obj_t arg) { nlr_raise(mp_obj_new_exception_arg1(exc_type, arg)); } -NORETURN void mp_raise_StopIteration(mp_obj_t arg) { +MP_NORETURN void mp_raise_StopIteration(mp_obj_t arg) { if (arg == MP_OBJ_NULL) { mp_raise_type(&mp_type_StopIteration); } else { @@ -1722,7 +1722,7 @@ NORETURN void mp_raise_StopIteration(mp_obj_t arg) { } } -NORETURN void mp_raise_TypeError_int_conversion(mp_const_obj_t arg) { +MP_NORETURN void mp_raise_TypeError_int_conversion(mp_const_obj_t arg) { #if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE (void)arg; mp_raise_TypeError(MP_ERROR_TEXT("can't convert to int")); @@ -1732,11 +1732,11 @@ NORETURN void mp_raise_TypeError_int_conversion(mp_const_obj_t arg) { #endif } -NORETURN void mp_raise_OSError(int errno_) { +MP_NORETURN void mp_raise_OSError(int errno_) { mp_raise_type_arg(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(errno_)); } -NORETURN void mp_raise_OSError_with_filename(int errno_, const char *filename) { +MP_NORETURN void mp_raise_OSError_with_filename(int errno_, const char *filename) { vstr_t vstr; vstr_init(&vstr, 32); vstr_printf(&vstr, "can't open %s", filename); @@ -1746,7 +1746,7 @@ NORETURN void mp_raise_OSError_with_filename(int errno_, const char *filename) { } #if MICROPY_STACK_CHECK || MICROPY_ENABLE_PYSTACK -NORETURN void mp_raise_recursion_depth(void) { +MP_NORETURN void mp_raise_recursion_depth(void) { mp_raise_type_arg(&mp_type_RuntimeError, MP_OBJ_NEW_QSTR(MP_QSTR_maximum_space_recursion_space_depth_space_exceeded)); } #endif diff --git a/py/runtime.h b/py/runtime.h index ffbc3972a32dc..064bd021d780d 100644 --- a/py/runtime.h +++ b/py/runtime.h @@ -136,8 +136,8 @@ static inline void mp_arg_check_num(size_t n_args, size_t n_kw, size_t n_args_mi } void mp_arg_parse_all(size_t n_pos, const mp_obj_t *pos, mp_map_t *kws, size_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals); void mp_arg_parse_all_kw_array(size_t n_pos, size_t n_kw, const mp_obj_t *args, size_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals); -NORETURN void mp_arg_error_terse_mismatch(void); -NORETURN void mp_arg_error_unimpl_kw(void); +MP_NORETURN void mp_arg_error_terse_mismatch(void); +MP_NORETURN void mp_arg_error_unimpl_kw(void); static inline mp_obj_dict_t *mp_locals_get(void) { return MP_STATE_THREAD(dict_locals); @@ -246,10 +246,10 @@ mp_obj_t mp_import_from(mp_obj_t module, qstr name); void mp_import_all(mp_obj_t module); #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_NONE -NORETURN void mp_raise_type(const mp_obj_type_t *exc_type); -NORETURN void mp_raise_ValueError_no_msg(void); -NORETURN void mp_raise_TypeError_no_msg(void); -NORETURN void mp_raise_NotImplementedError_no_msg(void); +MP_NORETURN void mp_raise_type(const mp_obj_type_t *exc_type); +MP_NORETURN void mp_raise_ValueError_no_msg(void); +MP_NORETURN void mp_raise_TypeError_no_msg(void); +MP_NORETURN void mp_raise_NotImplementedError_no_msg(void); #define mp_raise_msg(exc_type, msg) mp_raise_type(exc_type) #define mp_raise_msg_varg(exc_type, ...) mp_raise_type(exc_type) #define mp_raise_ValueError(msg) mp_raise_ValueError_no_msg() @@ -257,19 +257,19 @@ NORETURN void mp_raise_NotImplementedError_no_msg(void); #define mp_raise_NotImplementedError(msg) mp_raise_NotImplementedError_no_msg() #else #define mp_raise_type(exc_type) mp_raise_msg(exc_type, NULL) -NORETURN void mp_raise_msg(const mp_obj_type_t *exc_type, mp_rom_error_text_t msg); -NORETURN void mp_raise_msg_varg(const mp_obj_type_t *exc_type, mp_rom_error_text_t fmt, ...); -NORETURN void mp_raise_ValueError(mp_rom_error_text_t msg); -NORETURN void mp_raise_TypeError(mp_rom_error_text_t msg); -NORETURN void mp_raise_NotImplementedError(mp_rom_error_text_t msg); +MP_NORETURN void mp_raise_msg(const mp_obj_type_t *exc_type, mp_rom_error_text_t msg); +MP_NORETURN void mp_raise_msg_varg(const mp_obj_type_t *exc_type, mp_rom_error_text_t fmt, ...); +MP_NORETURN void mp_raise_ValueError(mp_rom_error_text_t msg); +MP_NORETURN void mp_raise_TypeError(mp_rom_error_text_t msg); +MP_NORETURN void mp_raise_NotImplementedError(mp_rom_error_text_t msg); #endif -NORETURN void mp_raise_type_arg(const mp_obj_type_t *exc_type, mp_obj_t arg); -NORETURN void mp_raise_StopIteration(mp_obj_t arg); -NORETURN void mp_raise_TypeError_int_conversion(mp_const_obj_t arg); -NORETURN void mp_raise_OSError(int errno_); -NORETURN void mp_raise_OSError_with_filename(int errno_, const char *filename); -NORETURN void mp_raise_recursion_depth(void); +MP_NORETURN void mp_raise_type_arg(const mp_obj_type_t *exc_type, mp_obj_t arg); +MP_NORETURN void mp_raise_StopIteration(mp_obj_t arg); +MP_NORETURN void mp_raise_TypeError_int_conversion(mp_const_obj_t arg); +MP_NORETURN void mp_raise_OSError(int errno_); +MP_NORETURN void mp_raise_OSError_with_filename(int errno_, const char *filename); +MP_NORETURN void mp_raise_recursion_depth(void); #if MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG #undef mp_check_self diff --git a/shared/libc/abort_.c b/shared/libc/abort_.c index 3051eae81e0ce..54eab67d3fb49 100644 --- a/shared/libc/abort_.c +++ b/shared/libc/abort_.c @@ -1,7 +1,7 @@ #include -NORETURN void abort_(void); +MP_NORETURN void abort_(void); -NORETURN void abort_(void) { +MP_NORETURN void abort_(void) { mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("abort() called")); } From 9032491efd8a63e0b13dbcfb8579cde4791c03af Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 25 Jan 2024 09:09:06 -0600 Subject: [PATCH 095/154] py/objstr: Add support for the :_b/o/x specifier in str.format. This groups non-decimal values by fours, such as bbb_bbbb_bbbb. It also supports `{:_d}` to use underscore for decimal numbers (grouped in threes). Use of incorrect ":,b" is not diagnosed. Thanks to @dpgeorge for the suggestion to reduce code size. Signed-off-by: Jeff Epler --- py/mpprint.c | 11 ++++------- py/mpprint.h | 14 +++++++------- py/mpz.c | 4 +++- py/objint.c | 5 +++-- py/objstr.c | 7 ++++--- py/runtime.h | 2 +- tests/basics/string_format.py | 10 ++++++++++ 7 files changed, 32 insertions(+), 21 deletions(-) diff --git a/py/mpprint.c b/py/mpprint.c index 291e4145ff0cd..00a5f944c69ef 100644 --- a/py/mpprint.c +++ b/py/mpprint.c @@ -58,7 +58,7 @@ int mp_print_str(const mp_print_t *print, const char *str) { return len; } -int mp_print_strn(const mp_print_t *print, const char *str, size_t len, int flags, char fill, int width) { +int mp_print_strn(const mp_print_t *print, const char *str, size_t len, unsigned int flags, char fill, int width) { int left_pad = 0; int right_pad = 0; int pad = width - len; @@ -201,7 +201,7 @@ static int mp_print_int(const mp_print_t *print, mp_uint_t x, int sgn, int base, return len; } -int mp_print_mp_int(const mp_print_t *print, mp_obj_t x, int base, int base_char, int flags, char fill, int width, int prec) { +int mp_print_mp_int(const mp_print_t *print, mp_obj_t x, unsigned int base, int base_char, int flags, char fill, int width, int prec) { // These are the only values for "base" that are required to be supported by this // function, since Python only allows the user to format integers in these bases. // If needed this function could be generalised to handle other values. @@ -248,10 +248,7 @@ int mp_print_mp_int(const mp_print_t *print, mp_obj_t x, int base, int base_char int prefix_len = prefix - prefix_buf; prefix = prefix_buf; - char comma = '\0'; - if (flags & PF_FLAG_SHOW_COMMA) { - comma = ','; - } + char comma = flags >> PF_FLAG_SEP_POS; // The size of this buffer is rather arbitrary. If it's not large // enough, a dynamic one will be allocated. @@ -340,7 +337,7 @@ int mp_print_mp_int(const mp_print_t *print, mp_obj_t x, int base, int base_char } #if MICROPY_PY_BUILTINS_FLOAT -int mp_print_float(const mp_print_t *print, mp_float_t f, char fmt, int flags, char fill, int width, int prec) { +int mp_print_float(const mp_print_t *print, mp_float_t f, char fmt, unsigned int flags, char fill, int width, int prec) { char buf[32]; char sign = '\0'; int chrs = 0; diff --git a/py/mpprint.h b/py/mpprint.h index 8383ea85794c2..511af329baf8d 100644 --- a/py/mpprint.h +++ b/py/mpprint.h @@ -33,11 +33,11 @@ #define PF_FLAG_SPACE_SIGN (0x004) #define PF_FLAG_NO_TRAILZ (0x008) #define PF_FLAG_SHOW_PREFIX (0x010) -#define PF_FLAG_SHOW_COMMA (0x020) -#define PF_FLAG_PAD_AFTER_SIGN (0x040) -#define PF_FLAG_CENTER_ADJUST (0x080) -#define PF_FLAG_ADD_PERCENT (0x100) -#define PF_FLAG_SHOW_OCTAL_LETTER (0x200) +#define PF_FLAG_PAD_AFTER_SIGN (0x020) +#define PF_FLAG_CENTER_ADJUST (0x040) +#define PF_FLAG_ADD_PERCENT (0x080) +#define PF_FLAG_SHOW_OCTAL_LETTER (0x100) +#define PF_FLAG_SEP_POS (9) // must be above all the above PF_FLAGs #if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES #define MP_PYTHON_PRINTER &mp_sys_stdout_print @@ -69,9 +69,9 @@ extern const mp_print_t mp_sys_stdout_print; #endif int mp_print_str(const mp_print_t *print, const char *str); -int mp_print_strn(const mp_print_t *print, const char *str, size_t len, int flags, char fill, int width); +int mp_print_strn(const mp_print_t *print, const char *str, size_t len, unsigned int flags, char fill, int width); #if MICROPY_PY_BUILTINS_FLOAT -int mp_print_float(const mp_print_t *print, mp_float_t f, char fmt, int flags, char fill, int width, int prec); +int mp_print_float(const mp_print_t *print, mp_float_t f, char fmt, unsigned int flags, char fill, int width, int prec); #endif int mp_printf(const mp_print_t *print, const char *fmt, ...); diff --git a/py/mpz.c b/py/mpz.c index 084aebda9ecaa..471bd1598188e 100644 --- a/py/mpz.c +++ b/py/mpz.c @@ -1672,6 +1672,8 @@ size_t mpz_as_str_inpl(const mpz_t *i, unsigned int base, const char *prefix, ch size_t ilen = i->len; + int n_comma = (base == 10) ? 3 : 4; + char *s = str; if (ilen == 0) { if (prefix) { @@ -1717,7 +1719,7 @@ size_t mpz_as_str_inpl(const mpz_t *i, unsigned int base, const char *prefix, ch break; } } - if (!done && comma && (s - last_comma) == 3) { + if (!done && comma && (s - last_comma) == n_comma) { *s++ = comma; last_comma = s; } diff --git a/py/objint.c b/py/objint.c index 4be6009a440e4..87d8a27852d34 100644 --- a/py/objint.c +++ b/py/objint.c @@ -209,7 +209,7 @@ static const uint8_t log_base2_floor[] = { size_t mp_int_format_size(size_t num_bits, int base, const char *prefix, char comma) { assert(2 <= base && base <= 16); size_t num_digits = num_bits / log_base2_floor[base - 1] + 1; - size_t num_commas = comma ? num_digits / 3 : 0; + size_t num_commas = comma ? (base == 10 ? num_digits / 3 : num_digits / 4): 0; size_t prefix_len = prefix ? strlen(prefix) : 0; return num_digits + num_commas + prefix_len + 2; // +1 for sign, +1 for null byte } @@ -251,6 +251,7 @@ char *mp_obj_int_formatted(char **buf, size_t *buf_size, size_t *fmt_size, mp_co sign = '-'; } + int n_comma = (base == 10) ? 3 : 4; size_t needed_size = mp_int_format_size(sizeof(fmt_int_t) * 8, base, prefix, comma); if (needed_size > *buf_size) { *buf = m_new(char, needed_size); @@ -275,7 +276,7 @@ char *mp_obj_int_formatted(char **buf, size_t *buf_size, size_t *fmt_size, mp_co c += '0'; } *(--b) = c; - if (comma && num != 0 && b > str && (last_comma - b) == 3) { + if (comma && num != 0 && b > str && (last_comma - b) == n_comma) { *(--b) = comma; last_comma = b; } diff --git a/py/objstr.c b/py/objstr.c index fda31d53185f9..c81fc682fd4e8 100644 --- a/py/objstr.c +++ b/py/objstr.c @@ -1184,7 +1184,7 @@ static vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar int width = -1; int precision = -1; char type = '\0'; - int flags = 0; + unsigned int flags = 0; if (format_spec) { // The format specifier (from http://docs.python.org/2/library/string.html#formatspec) @@ -1229,8 +1229,9 @@ static vstr_t mp_obj_str_format_helper(const char *str, const char *top, int *ar } } s = str_to_int(s, stop, &width); - if (*s == ',') { - flags |= PF_FLAG_SHOW_COMMA; + if (*s == ',' || *s == '_') { + MP_STATIC_ASSERT((unsigned)'_' << PF_FLAG_SEP_POS >> PF_FLAG_SEP_POS == '_'); + flags |= (unsigned)*s << PF_FLAG_SEP_POS; s++; } if (*s == '.') { diff --git a/py/runtime.h b/py/runtime.h index 064bd021d780d..a93488e2cdc04 100644 --- a/py/runtime.h +++ b/py/runtime.h @@ -128,7 +128,7 @@ void mp_event_wait_indefinite(void); void mp_event_wait_ms(mp_uint_t timeout_ms); // extra printing method specifically for mp_obj_t's which are integral type -int mp_print_mp_int(const mp_print_t *print, mp_obj_t x, int base, int base_char, int flags, char fill, int width, int prec); +int mp_print_mp_int(const mp_print_t *print, mp_obj_t x, unsigned base, int base_char, int flags, char fill, int width, int prec); void mp_arg_check_num_sig(size_t n_args, size_t n_kw, uint32_t sig); static inline void mp_arg_check_num(size_t n_args, size_t n_kw, size_t n_args_min, size_t n_args_max, bool takes_kw) { diff --git a/tests/basics/string_format.py b/tests/basics/string_format.py index e8600f5836139..11e7836a73ece 100644 --- a/tests/basics/string_format.py +++ b/tests/basics/string_format.py @@ -22,7 +22,17 @@ def test(fmt, *args): test("{:4x}", 123) test("{:4X}", 123) +test("{:4,d}", 1) +test("{:4_d}", 1) +test("{:4_o}", 1) +test("{:4_b}", 1) +test("{:4_x}", 1) + test("{:4,d}", 12345678) +test("{:4_d}", 12345678) +test("{:4_o}", 12345678) +test("{:4_b}", 12345678) +test("{:4_x}", 12345678) test("{:#4b}", 10) test("{:#4o}", 123) From f77fd6257cc14ff9a05d702406501a49b7bacbd0 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Mon, 12 May 2025 17:39:16 +0200 Subject: [PATCH 096/154] tests/cpydiff: Document format separator difference. Signed-off-by: Jeff Epler --- tests/cpydiff/types_str_formatsep.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/cpydiff/types_str_formatsep.py diff --git a/tests/cpydiff/types_str_formatsep.py b/tests/cpydiff/types_str_formatsep.py new file mode 100644 index 0000000000000..05d0b8d3d2cf4 --- /dev/null +++ b/tests/cpydiff/types_str_formatsep.py @@ -0,0 +1,19 @@ +""" +categories: Types,str +description: MicroPython accepts the "," grouping option with any radix, unlike CPython +cause: To reduce code size, MicroPython does not issue an error for this combination +workaround: Do not use a format string like ``{:,b}`` if CPython compatibility is required. +""" + +try: + print("{:,b}".format(99)) +except ValueError: + print("ValueError") +try: + print("{:,x}".format(99)) +except ValueError: + print("ValueError") +try: + print("{:,o}".format(99)) +except ValueError: + print("ValueError") From d7371124d288cc85d2386b63ea39cfa4fa803194 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 21 Mar 2025 10:44:20 +0100 Subject: [PATCH 097/154] esp32/esp32_common.cmake: Use the tinyusb source files from ESP-IDF. This commit removes the explicit dependency on the vendored tinyusb version for the ESP32S2 and ESP32S3 boards. Tinyusb is still available to MicroPython through a dependency on the `espressif/esp_tinyusb` ESP-IDF component, which in turn depends on the `espressif/tinyusb` component itself. Signed-off-by: Alessandro Gatti --- ports/esp32/esp32_common.cmake | 9 --------- 1 file changed, 9 deletions(-) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index c54f5a5403226..063e8d6b92f6a 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -80,9 +80,7 @@ list(APPEND MICROPY_SOURCE_DRIVERS ${MICROPY_DIR}/drivers/dht/dht.c ) -list(APPEND GIT_SUBMODULES lib/tinyusb) if(MICROPY_PY_TINYUSB) - set(TINYUSB_SRC "${MICROPY_DIR}/lib/tinyusb/src") string(TOUPPER OPT_MCU_${IDF_TARGET} tusb_mcu) list(APPEND MICROPY_DEF_TINYUSB @@ -90,12 +88,6 @@ if(MICROPY_PY_TINYUSB) ) list(APPEND MICROPY_SOURCE_TINYUSB - ${TINYUSB_SRC}/tusb.c - ${TINYUSB_SRC}/common/tusb_fifo.c - ${TINYUSB_SRC}/device/usbd.c - ${TINYUSB_SRC}/device/usbd_control.c - ${TINYUSB_SRC}/class/cdc/cdc_device.c - ${TINYUSB_SRC}/portable/synopsys/dwc2/dcd_dwc2.c ${MICROPY_DIR}/shared/tinyusb/mp_usbd.c ${MICROPY_DIR}/shared/tinyusb/mp_usbd_cdc.c ${MICROPY_DIR}/shared/tinyusb/mp_usbd_descriptor.c @@ -103,7 +95,6 @@ if(MICROPY_PY_TINYUSB) ) list(APPEND MICROPY_INC_TINYUSB - ${TINYUSB_SRC} ${MICROPY_DIR}/shared/tinyusb/ ) From 1d4bf8ac40236e22d0a67b103b4330d1ee6659d4 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 29 Mar 2025 09:12:28 +0100 Subject: [PATCH 098/154] esp32/machine_timer: Fix timer.value() for an uninitialized timer. Raises a value error in that case, which happens after a timer was created but not initialized, or after calling `timer.deinit()`. Fixes issue #17033. Signed-off-by: robert-hh --- ports/esp32/machine_timer.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/esp32/machine_timer.c b/ports/esp32/machine_timer.c index 82b432bbacddd..34d49c79d2894 100644 --- a/ports/esp32/machine_timer.c +++ b/ports/esp32/machine_timer.c @@ -246,6 +246,9 @@ static MP_DEFINE_CONST_FUN_OBJ_KW(machine_timer_init_obj, 1, machine_timer_init) static mp_obj_t machine_timer_value(mp_obj_t self_in) { machine_timer_obj_t *self = self_in; + if (self->handle == NULL) { + mp_raise_ValueError(MP_ERROR_TEXT("timer not set")); + } uint64_t result = timer_ll_get_counter_value(self->hal_context.dev, self->index); return MP_OBJ_NEW_SMALL_INT((mp_uint_t)(result / (TIMER_SCALE / 1000))); // value in ms } From 116d0d494589e470b4003fcecd18162c9eed7e9b Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 25 Mar 2025 21:53:45 +0100 Subject: [PATCH 099/154] esp32/esp32_common.cmake: Allow adding defines and compiler flags. This commit introduces two extra CMake variables, MICROPY_DEF_COMPONENT and MICROPY_COMPILE_COMPONENT, that make it easier to integrate MicroPython as a custom ESP-IDF component. Whilst there is no official MicroPython component available for ESP-IDF, integration can be achieved with some minor CMake scripting outside the MicroPython tree - except for customisation of compilation defines and build flags, which is what this commit tries to provide. Compilation defines customisation is especially important for MicroPython configuration, as it is not possible to inject a value for MP_CONFIGFILE otherwise. This means that unless MicroPython itself is forked first to edit ports/esp32/mpconfigport.h, it is not possible to perform any meaningful configuration of the interpreter/runtime when included as a component. Signed-off-by: Alessandro Gatti --- ports/esp32/esp32_common.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 063e8d6b92f6a..2e7b95b385be5 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -242,6 +242,7 @@ endif() # Set compile options for this port. target_compile_definitions(${MICROPY_TARGET} PUBLIC + ${MICROPY_DEF_COMPONENT} ${MICROPY_DEF_CORE} ${MICROPY_DEF_BOARD} ${MICROPY_DEF_TINYUSB} @@ -254,6 +255,7 @@ target_compile_definitions(${MICROPY_TARGET} PUBLIC # Disable some warnings to keep the build output clean. target_compile_options(${MICROPY_TARGET} PUBLIC + ${MICROPY_COMPILE_COMPONENT} -Wno-clobbered -Wno-deprecated-declarations -Wno-missing-field-initializers From 883dc41d465a6a652b607b7982d8eb6be39fa7f4 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Tue, 25 Mar 2025 22:43:23 +0100 Subject: [PATCH 100/154] esp32/main: Make the entry point function name configurable. This commit introduces a new port configuration entry allowing the entry point function name to be changed, from "app_main" to a custom name. This is needed when MicroPython is embedded as an ESP-IDF component, since the "app_main" symbol is already provided elsewhere, making compilation not possible. Marking MicroPython's symbol as weak would make it compile and make it possible to create and start the MicroPython task anyway with the right FreeRTOS task creation incantation, but it is probably easier to just rename the initialisation function into something else that can be accessed from outside. When MicroPython is embedded as an ESP-IDF component, the MICROPY_ESP_IDF_ENTRY definition can be set to indicate the new entry point function name. The new function name prototype should still be defined in external code to let linking succeed. Also, the NLR failure callback is marked as weak to give the chance of handling such error in a more controlled fashion rather than trigger an unconditional board restart. Signed-off-by: Alessandro Gatti --- ports/esp32/main.c | 4 ++-- ports/esp32/mpconfigport.h | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ports/esp32/main.c b/ports/esp32/main.c index 4f0c27ee078f8..b8f49a33baaee 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -216,7 +216,7 @@ void boardctrl_startup(void) { } } -void app_main(void) { +void MICROPY_ESP_IDF_ENTRY(void) { // Hook for a board to run code at start up. // This defaults to initialising NVS. MICROPY_BOARD_STARTUP(); @@ -225,7 +225,7 @@ void app_main(void) { xTaskCreatePinnedToCore(mp_task, "mp_task", MICROPY_TASK_STACK_SIZE / sizeof(StackType_t), NULL, MP_TASK_PRIORITY, &mp_main_task_handle, MP_TASK_COREID); } -void nlr_jump_fail(void *val) { +MP_WEAK void nlr_jump_fail(void *val) { printf("NLR jump failed, val=%p\n", val); esp_restart(); } diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index b5b7d63a56333..7c26009578928 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -391,3 +391,8 @@ void boardctrl_startup(void); #ifndef MICROPY_PY_STRING_TX_GIL_THRESHOLD #define MICROPY_PY_STRING_TX_GIL_THRESHOLD (20) #endif + +// Code can override this to provide a custom ESP-IDF entry point. +#ifndef MICROPY_ESP_IDF_ENTRY +#define MICROPY_ESP_IDF_ENTRY app_main +#endif From 155fa94fbf6afda9dbb3b8508c093648b5bffa09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Thu, 6 Mar 2025 18:52:54 +0100 Subject: [PATCH 101/154] esp32/machine_uart: Correctly manage UART queue and event task. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the driver was reinitialised while there was already an event task running the queue that task is trying to receive from would be deleted, causing it to try to take a lock that no longer existed and deadlocking the CPU. This change ensures the task is always shut down before recreating the queue and recreates the task afterwards. It also allows setting an IRQ handler before the UART is initialized (like other ports allow), removes the task when the UART is deinitialized (which was previously missing), adds a check that no event task can be started when no queue exists, and adds a check to prevent reinitialising the UART driver unnecessarily. Signed-off-by: DaniĆ«l van de Giessen --- ports/esp32/machine_uart.c | 39 ++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/ports/esp32/machine_uart.c b/ports/esp32/machine_uart.c index 73089ef463681..e5857e894b90e 100644 --- a/ports/esp32/machine_uart.c +++ b/ports/esp32/machine_uart.c @@ -59,6 +59,7 @@ #define UART_IRQ_BREAK (1 << UART_BREAK) #define MP_UART_ALLOWED_FLAGS (UART_IRQ_RX | UART_IRQ_RXIDLE | UART_IRQ_BREAK) #define RXIDLE_TIMER_MIN (5000) // 500 us +#define UART_QUEUE_SIZE (3) enum { RXIDLE_INACTIVE, @@ -174,6 +175,13 @@ static void uart_event_task(void *self_in) { } } +static inline void uart_event_task_create(machine_uart_obj_t *self) { + if (xTaskCreatePinnedToCore(uart_event_task, "uart_event_task", 2048, self, + ESP_TASKD_EVENT_PRIO, (TaskHandle_t *)&self->uart_event_task, MP_TASK_COREID) != pdPASS) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("failed to create UART event task")); + } +} + static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); uint32_t baudrate; @@ -250,7 +258,7 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, // wait for all data to be transmitted before changing settings uart_wait_tx_done(self->uart_num, pdMS_TO_TICKS(1000)); - if (args[ARG_txbuf].u_int >= 0 || args[ARG_rxbuf].u_int >= 0) { + if ((args[ARG_txbuf].u_int >= 0 && args[ARG_txbuf].u_int != self->txbuf) || (args[ARG_rxbuf].u_int >= 0 && args[ARG_rxbuf].u_int != self->rxbuf)) { // must reinitialise driver to change the tx/rx buffer size #if MICROPY_HW_ENABLE_UART_REPL if (self->uart_num == MICROPY_HW_UART_REPL) { @@ -275,9 +283,12 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, check_esp_err(uart_get_word_length(self->uart_num, &uartcfg.data_bits)); check_esp_err(uart_get_parity(self->uart_num, &uartcfg.parity)); check_esp_err(uart_get_stop_bits(self->uart_num, &uartcfg.stop_bits)); - check_esp_err(uart_driver_delete(self->uart_num)); + mp_machine_uart_deinit(self); check_esp_err(uart_param_config(self->uart_num, &uartcfg)); - check_esp_err(uart_driver_install(self->uart_num, self->rxbuf, self->txbuf, 0, NULL, 0)); + check_esp_err(uart_driver_install(self->uart_num, self->rxbuf, self->txbuf, UART_QUEUE_SIZE, &self->uart_queue, 0)); + if (self->mp_irq_obj != NULL && self->mp_irq_obj->handler != mp_const_none) { + uart_event_task_create(self); + } } // set baudrate @@ -437,7 +448,8 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg self->timeout_char = 0; self->invert = 0; self->flowcontrol = 0; - self->uart_event_task = 0; + self->uart_event_task = NULL; + self->uart_queue = NULL; self->rxidle_state = RXIDLE_INACTIVE; switch (uart_num) { @@ -470,12 +482,13 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg { // Remove any existing configuration check_esp_err(uart_driver_delete(self->uart_num)); + self->uart_queue = NULL; // init the peripheral // Setup check_esp_err(uart_param_config(self->uart_num, &uartcfg)); - check_esp_err(uart_driver_install(uart_num, self->rxbuf, self->txbuf, 3, &self->uart_queue, 0)); + check_esp_err(uart_driver_install(uart_num, self->rxbuf, self->txbuf, UART_QUEUE_SIZE, &self->uart_queue, 0)); } mp_map_t kw_args; @@ -489,7 +502,12 @@ static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_arg } static void mp_machine_uart_deinit(machine_uart_obj_t *self) { + if (self->uart_event_task != NULL) { + vTaskDelete(self->uart_event_task); + self->uart_event_task = NULL; + } check_esp_err(uart_driver_delete(self->uart_num)); + self->uart_queue = NULL; } static mp_int_t mp_machine_uart_any(machine_uart_obj_t *self) { @@ -568,6 +586,12 @@ static const mp_irq_methods_t uart_irq_methods = { }; static mp_irq_obj_t *mp_machine_uart_irq(machine_uart_obj_t *self, bool any_args, mp_arg_val_t *args) { + #if MICROPY_HW_ENABLE_UART_REPL + if (self->uart_num == MICROPY_HW_UART_REPL) { + mp_raise_ValueError(MP_ERROR_TEXT("UART does not support IRQs")); + } + #endif + if (self->mp_irq_obj == NULL) { self->mp_irq_trigger = 0; self->mp_irq_obj = mp_irq_new(&uart_irq_methods, MP_OBJ_FROM_PTR(self)); @@ -597,9 +621,8 @@ static mp_irq_obj_t *mp_machine_uart_irq(machine_uart_obj_t *self, bool any_args uart_irq_configure_timer(self, trigger); // Start a task for handling events - if (handler != mp_const_none && self->uart_event_task == NULL) { - xTaskCreatePinnedToCore(uart_event_task, "uart_event_task", 2048, self, - ESP_TASKD_EVENT_PRIO, (TaskHandle_t *)&self->uart_event_task, MP_TASK_COREID); + if (handler != mp_const_none && self->uart_event_task == NULL && self->uart_queue != NULL) { + uart_event_task_create(self); } else if (handler == mp_const_none && self->uart_event_task != NULL) { vTaskDelete(self->uart_event_task); self->uart_event_task = NULL; From 24065824796b54d42fdb93457bf53d21656e473e Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Fri, 28 Mar 2025 15:30:29 +0100 Subject: [PATCH 102/154] esp32/network_common: Raise a memory error on ESP_ERR_NO_MEM. This commit changes the error handler for WiFi operations to recognise out of memory conditions reported by ESP-IDF functions, and report them as more descriptive exceptions rather than a generic "error 0x101". The error handler only provided a human-readable error description for WiFi-specific error codes (codes in the ESP_ERR_WIFI_BASE range), but WiFi functions are known to return other codes. Now ESP_ERR_NO_MEM is covered with a specific error message, making it easier to debug issues related to running out of ESP-IDF heap. Signed-off-by: Alessandro Gatti --- ports/esp32/network_common.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/esp32/network_common.c b/ports/esp32/network_common.c index e07d7a993f25f..bd34f1b41fdfc 100644 --- a/ports/esp32/network_common.c +++ b/ports/esp32/network_common.c @@ -77,6 +77,8 @@ MP_NORETURN void esp_exceptions_helper(esp_err_t e) { mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Wifi Would Block")); case ESP_ERR_WIFI_NOT_CONNECT: mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Wifi Not Connected")); + case ESP_ERR_NO_MEM: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("WiFi Out of Memory")); default: mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("Wifi Unknown Error 0x%04x"), e); } From 3b1e22c66947271e8b60eddf4e8aa6dadc6d9a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Mon, 27 May 2024 14:25:23 +0200 Subject: [PATCH 103/154] esp32/network_ppp: Restructure to match extmod/network_ppp_lwip. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ESP32 PPP implementation predates the generic implementation in extmod. The new extmod implementation has a few advantages such as a better deinitialisation procedure (the ESP32 implemementation would not clean up properly and cause crashes if recreated) and using the UART IRQ functionality instead of running a task to read data from the UART. This change restructures the ESP implementation to be much closer to the new extmod version, while also bringing a few tiny improvements from the ESP32 version to the extmod version. The diff between extmod/network_ppp_lwip.c and ports/esp32/network_ppp.c is now a small set of easy to review ESP32 port-specific changes. Signed-off-by: DaniĆ«l van de Giessen --- extmod/network_ppp_lwip.c | 9 +- ports/esp32/modnetwork.h | 2 +- ports/esp32/modnetwork_globals.h | 2 +- ports/esp32/network_ppp.c | 511 ++++++++++++++++++------------- 4 files changed, 310 insertions(+), 214 deletions(-) diff --git a/extmod/network_ppp_lwip.c b/extmod/network_ppp_lwip.c index 8eb90ea4a6529..2c3dac92012a0 100644 --- a/extmod/network_ppp_lwip.c +++ b/extmod/network_ppp_lwip.c @@ -24,6 +24,10 @@ * THE SOFTWARE. */ +// This file is intended to closely match ports/esp32/network_ppp.c. Changes can +// and should probably be applied to both files. Compare them directly by using: +// git diff --no-index extmod/network_ppp_lwip.c ports/esp32/network_ppp.c + #include "py/runtime.h" #include "py/mphal.h" #include "py/stream.h" @@ -80,7 +84,6 @@ static void network_ppp_status_cb(ppp_pcb *pcb, int err_code, void *ctx) { break; case PPPERR_USER: if (self->state >= STATE_ERROR) { - network_ppp_stream_uart_irq_disable(self); // Indicate that we are no longer connected and thus // only need to free the PPP PCB, not close it. self->state = STATE_ACTIVE; @@ -121,6 +124,7 @@ static mp_obj_t network_ppp___del__(mp_obj_t self_in) { self->state = STATE_INACTIVE; ppp_close(self->pcb, 1); } + network_ppp_stream_uart_irq_disable(self); // Free PPP PCB and reset state. self->state = STATE_INACTIVE; ppp_free(self->pcb); @@ -295,7 +299,8 @@ static mp_obj_t network_ppp_connect(size_t n_args, const mp_obj_t *args, mp_map_ ppp_set_auth(self->pcb, parsed_args[ARG_security].u_int, user_str, key_str); } - netif_set_default(self->pcb->netif); + ppp_set_default(self->pcb); + ppp_set_usepeerdns(self->pcb, true); if (ppp_connect(self->pcb, 0) != ERR_OK) { diff --git a/ports/esp32/modnetwork.h b/ports/esp32/modnetwork.h index 308a515c6e249..383e200b983c2 100644 --- a/ports/esp32/modnetwork.h +++ b/ports/esp32/modnetwork.h @@ -52,7 +52,7 @@ extern const mp_obj_type_t esp_network_wlan_type; MP_DECLARE_CONST_FUN_OBJ_0(esp_network_initialize_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(esp_network_get_wlan_obj); MP_DECLARE_CONST_FUN_OBJ_KW(esp_network_get_lan_obj); -MP_DECLARE_CONST_FUN_OBJ_1(esp_network_ppp_make_new_obj); +extern const struct _mp_obj_type_t esp_network_ppp_lwip_type; MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(esp_network_ifconfig_obj); MP_DECLARE_CONST_FUN_OBJ_KW(esp_network_ipconfig_obj); MP_DECLARE_CONST_FUN_OBJ_KW(esp_nic_ipconfig_obj); diff --git a/ports/esp32/modnetwork_globals.h b/ports/esp32/modnetwork_globals.h index 1aad785ee3d9c..9e355e958f1a4 100644 --- a/ports/esp32/modnetwork_globals.h +++ b/ports/esp32/modnetwork_globals.h @@ -8,7 +8,7 @@ { MP_ROM_QSTR(MP_QSTR_LAN), MP_ROM_PTR(&esp_network_get_lan_obj) }, #endif #if defined(CONFIG_ESP_NETIF_TCPIP_LWIP) && defined(CONFIG_LWIP_PPP_SUPPORT) -{ MP_ROM_QSTR(MP_QSTR_PPP), MP_ROM_PTR(&esp_network_ppp_make_new_obj) }, +{ MP_ROM_QSTR(MP_QSTR_PPP), MP_ROM_PTR(&esp_network_ppp_lwip_type) }, #endif { MP_ROM_QSTR(MP_QSTR_phy_mode), MP_ROM_PTR(&esp_network_phy_mode_obj) }, { MP_ROM_QSTR(MP_QSTR_ipconfig), MP_ROM_PTR(&esp_network_ipconfig_obj) }, diff --git a/ports/esp32/network_ppp.c b/ports/esp32/network_ppp.c index 4dd5a3718c295..8b700c98ef386 100644 --- a/ports/esp32/network_ppp.c +++ b/ports/esp32/network_ppp.c @@ -4,6 +4,7 @@ * The MIT License (MIT) * * Copyright (c) 2018 "Eric Poulsen" + * Copyright (c) 2024 Damien P. George * * Based on the ESP IDF example code which is Public Domain / CC0 * @@ -26,172 +27,265 @@ * THE SOFTWARE. */ +// This file is intended to closely match extmod/network_ppp_lwip.c. Changes can +// and should probably be applied to both files. Compare them directly by using: +// git diff --no-index extmod/network_ppp_lwip.c ports/esp32/network_ppp.c + #include "py/runtime.h" #include "py/mphal.h" -#include "py/objtype.h" #include "py/stream.h" -#include "shared/netutils/netutils.h" -#include "modmachine.h" -#include "ppp_set_auth.h" -#include "netif/ppp/ppp.h" -#include "netif/ppp/pppos.h" -#include "lwip/err.h" -#include "lwip/sockets.h" -#include "lwip/sys.h" -#include "lwip/netdb.h" +#if defined(CONFIG_ESP_NETIF_TCPIP_LWIP) && defined(CONFIG_LWIP_PPP_SUPPORT) + #include "lwip/dns.h" +#include "netif/ppp/ppp.h" #include "netif/ppp/pppapi.h" +#include "netif/ppp/pppos.h" -#if defined(CONFIG_ESP_NETIF_TCPIP_LWIP) && defined(CONFIG_LWIP_PPP_SUPPORT) +// Includes for port-specific changes compared to network_ppp_lwip.c +#include "shared/netutils/netutils.h" +#include "ppp_set_auth.h" -#define PPP_CLOSE_TIMEOUT_MS (4000) +// Enable this to see the serial data going between the PPP layer. +#define PPP_TRACE_IN_OUT (0) -typedef struct _ppp_if_obj_t { +typedef enum { + STATE_INACTIVE, + STATE_ACTIVE, + STATE_ERROR, + STATE_CONNECTING, + STATE_CONNECTED, +} network_ppp_state_t; + +typedef struct _network_ppp_obj_t { mp_obj_base_t base; - bool active; - bool connected; - volatile bool clean_close; - ppp_pcb *pcb; + network_ppp_state_t state; + int error_code; mp_obj_t stream; - SemaphoreHandle_t inactiveWaitSem; - volatile TaskHandle_t client_task_handle; - struct netif pppif; -} ppp_if_obj_t; + ppp_pcb *pcb; + struct netif netif; +} network_ppp_obj_t; -const mp_obj_type_t ppp_if_type; +const mp_obj_type_t esp_network_ppp_lwip_type; + +static mp_obj_t network_ppp___del__(mp_obj_t self_in); + +static void network_ppp_stream_uart_irq_disable(network_ppp_obj_t *self) { + if (self->stream == mp_const_none) { + return; + } -static void ppp_status_cb(ppp_pcb *pcb, int err_code, void *ctx) { - ppp_if_obj_t *self = ctx; - struct netif *pppif = ppp_netif(self->pcb); + // Disable UART IRQ. + mp_obj_t dest[3]; + mp_load_method(self->stream, MP_QSTR_irq, dest); + dest[2] = mp_const_none; + mp_call_method_n_kw(1, 0, dest); +} +static void network_ppp_status_cb(ppp_pcb *pcb, int err_code, void *ctx) { + network_ppp_obj_t *self = ctx; switch (err_code) { case PPPERR_NONE: - #if CONFIG_LWIP_IPV6 - self->connected = (pppif->ip_addr.u_addr.ip4.addr != 0); - #else - self->connected = (pppif->ip_addr.addr != 0); - #endif // CONFIG_LWIP_IPV6 + self->state = STATE_CONNECTED; break; case PPPERR_USER: - self->clean_close = true; - break; - case PPPERR_CONNECT: - self->connected = false; + if (self->state >= STATE_ERROR) { + // Indicate that we are no longer connected and thus + // only need to free the PPP PCB, not close it. + self->state = STATE_ACTIVE; + } + // Clean up the PPP PCB. + network_ppp___del__(MP_OBJ_FROM_PTR(self)); break; default: + self->state = STATE_ERROR; + self->error_code = err_code; break; } } -static mp_obj_t ppp_make_new(mp_obj_t stream) { +static mp_obj_t network_ppp_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + mp_arg_check_num(n_args, n_kw, 1, 1, false); + + mp_obj_t stream = all_args[0]; + if (stream != mp_const_none) { mp_get_stream_raise(stream, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); } - ppp_if_obj_t *self = mp_obj_malloc_with_finaliser(ppp_if_obj_t, &ppp_if_type); + network_ppp_obj_t *self = mp_obj_malloc_with_finaliser(network_ppp_obj_t, type); + self->state = STATE_INACTIVE; self->stream = stream; - self->active = false; - self->connected = false; - self->clean_close = false; - self->client_task_handle = NULL; + self->pcb = NULL; return MP_OBJ_FROM_PTR(self); } -MP_DEFINE_CONST_FUN_OBJ_1(esp_network_ppp_make_new_obj, ppp_make_new); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) -static u32_t ppp_output_callback(ppp_pcb *pcb, const void *data, u32_t len, void *ctx) -#else -static u32_t ppp_output_callback(ppp_pcb *pcb, u8_t *data, u32_t len, void *ctx) -#endif -{ - ppp_if_obj_t *self = ctx; - - mp_obj_t stream = self->stream; - if (stream == mp_const_none) { - return 0; +static mp_obj_t network_ppp___del__(mp_obj_t self_in) { + network_ppp_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->state >= STATE_ACTIVE) { + if (self->state >= STATE_ERROR) { + // Still connected over the stream. + // Force the connection to close, with nocarrier=1. + self->state = STATE_INACTIVE; + pppapi_close(self->pcb, 1); + } + network_ppp_stream_uart_irq_disable(self); + // Free PPP PCB and reset state. + self->state = STATE_INACTIVE; + pppapi_free(self->pcb); + self->pcb = NULL; } - - int err; - return mp_stream_rw(stream, (void *)data, len, &err, MP_STREAM_RW_WRITE); + return mp_const_none; } +static MP_DEFINE_CONST_FUN_OBJ_1(network_ppp___del___obj, network_ppp___del__); -static void pppos_client_task(void *self_in) { - ppp_if_obj_t *self = (ppp_if_obj_t *)self_in; - uint8_t buf[256]; +static mp_obj_t network_ppp_poll(size_t n_args, const mp_obj_t *args) { + network_ppp_obj_t *self = MP_OBJ_TO_PTR(args[0]); - int len = 0; - while (ulTaskNotifyTake(pdTRUE, len <= 0) == 0) { - mp_obj_t stream = self->stream; - if (stream == mp_const_none) { - len = 0; - } else { - int err; - len = mp_stream_rw(stream, buf, sizeof(buf), &err, 0); - if (len > 0) { - pppos_input_tcpip(self->pcb, (u8_t *)buf, len); - } - } + if (self->state <= STATE_ERROR) { + return MP_OBJ_NEW_SMALL_INT(-MP_EPERM); } - self->client_task_handle = NULL; - vTaskDelete(NULL); - for (;;) { + mp_int_t total_len = 0; + mp_obj_t stream = self->stream; + while (stream != mp_const_none) { + uint8_t buf[256]; + int err; + mp_uint_t len = mp_stream_rw(stream, buf, sizeof(buf), &err, 0); + if (len == 0) { + break; + } + #if PPP_TRACE_IN_OUT + mp_printf(&mp_plat_print, "ppp_in(n=%u,data=", len); + for (size_t i = 0; i < len; ++i) { + mp_printf(&mp_plat_print, "%02x:", buf[i]); + } + mp_printf(&mp_plat_print, ")\n"); + #endif + pppos_input(self->pcb, (u8_t *)buf, len); + total_len += len; } + + return MP_OBJ_NEW_SMALL_INT(total_len); } +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_ppp_poll_obj, 1, 2, network_ppp_poll); -static mp_obj_t ppp_active(size_t n_args, const mp_obj_t *args) { - ppp_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); +static void network_ppp_stream_uart_irq_enable(network_ppp_obj_t *self) { + if (self->stream == mp_const_none) { + return; + } - if (n_args > 1) { - if (mp_obj_is_true(args[1])) { - if (self->active) { - return mp_const_true; - } + // Enable UART IRQ to call PPP.poll() when incoming data is ready. + mp_obj_t dest[4]; + mp_load_method(self->stream, MP_QSTR_irq, dest); + dest[2] = mp_obj_new_bound_meth(MP_OBJ_FROM_PTR(&network_ppp_poll_obj), MP_OBJ_FROM_PTR(self)); + dest[3] = mp_load_attr(self->stream, MP_QSTR_IRQ_RXIDLE); + mp_call_method_n_kw(2, 0, dest); +} - self->pcb = pppapi_pppos_create(&self->pppif, ppp_output_callback, ppp_status_cb, self); +static mp_obj_t network_ppp_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + if (n_args != 1 && kwargs->used != 0) { + mp_raise_TypeError(MP_ERROR_TEXT("either pos or kw args are allowed")); + } + network_ppp_obj_t *self = MP_OBJ_TO_PTR(args[0]); - if (self->pcb == NULL) { - mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("init failed")); - } - self->active = true; - } else { - if (!self->active) { - return mp_const_false; + if (kwargs->used != 0) { + for (size_t i = 0; i < kwargs->alloc; i++) { + if (mp_map_slot_is_filled(kwargs, i)) { + switch (mp_obj_str_get_qstr(kwargs->table[i].key)) { + case MP_QSTR_stream: { + if (kwargs->table[i].value != mp_const_none) { + mp_get_stream_raise(kwargs->table[i].value, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + } + if (self->state >= STATE_ACTIVE) { + network_ppp_stream_uart_irq_disable(self); + } + self->stream = kwargs->table[i].value; + if (self->state >= STATE_ACTIVE) { + network_ppp_stream_uart_irq_enable(self); + } + break; + } + default: + break; + } } + } + return mp_const_none; + } - if (self->client_task_handle != NULL) { // is connecting or connected? - // Wait for PPPERR_USER, with timeout - pppapi_close(self->pcb, 0); - uint32_t t0 = mp_hal_ticks_ms(); - while (!self->clean_close && mp_hal_ticks_ms() - t0 < PPP_CLOSE_TIMEOUT_MS) { - mp_hal_delay_ms(10); - } + if (n_args != 2) { + mp_raise_TypeError(MP_ERROR_TEXT("can query only one param")); + } - // Shutdown task - xTaskNotifyGive(self->client_task_handle); - t0 = mp_hal_ticks_ms(); - while (self->client_task_handle != NULL && mp_hal_ticks_ms() - t0 < PPP_CLOSE_TIMEOUT_MS) { - mp_hal_delay_ms(10); + mp_obj_t val = mp_const_none; + + switch (mp_obj_str_get_qstr(args[1])) { + case MP_QSTR_stream: { + val = self->stream; + break; + } + case MP_QSTR_ifname: { + if (self->pcb != NULL) { + struct netif *pppif = ppp_netif(self->pcb); + char ifname[NETIF_NAMESIZE + 1] = {0}; + netif_index_to_name(netif_get_index(pppif), ifname); + if (ifname[0] != 0) { + val = mp_obj_new_str_from_cstr((char *)ifname); } } - - // Release PPP - pppapi_free(self->pcb); - self->pcb = NULL; - self->active = false; - self->connected = false; - self->clean_close = false; + break; } + default: + mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); + } + + return val; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(network_ppp_config_obj, 1, network_ppp_config); + +static mp_obj_t network_ppp_status(mp_obj_t self_in) { + network_ppp_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->state == STATE_ERROR) { + return MP_OBJ_NEW_SMALL_INT(-self->error_code); + } else { + return MP_OBJ_NEW_SMALL_INT(self->state); + } +} +static MP_DEFINE_CONST_FUN_OBJ_1(network_ppp_status_obj, network_ppp_status); + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) +static u32_t network_ppp_output_callback(ppp_pcb *pcb, const void *data, u32_t len, void *ctx) +#else +static u32_t network_ppp_output_callback(ppp_pcb *pcb, u8_t *data, u32_t len, void *ctx) +#endif +{ + network_ppp_obj_t *self = ctx; + #if PPP_TRACE_IN_OUT + mp_printf(&mp_plat_print, "ppp_out(n=%u,data=", len); + for (size_t i = 0; i < len; ++i) { + mp_printf(&mp_plat_print, "%02x:", ((const uint8_t *)data)[i]); + } + mp_printf(&mp_plat_print, ")\n"); + #endif + mp_obj_t stream = self->stream; + if (stream == mp_const_none) { + return 0; } - return mp_obj_new_bool(self->active); + int err; + // The return value from this output callback is the number of bytes written out. + // If it's less than the requested number of bytes then lwIP will propagate out an error. + return mp_stream_rw(stream, (void *)data, len, &err, MP_STREAM_RW_WRITE); } -static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ppp_active_obj, 1, 2, ppp_active); -static mp_obj_t ppp_connect_py(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { - enum { ARG_authmode, ARG_username, ARG_password }; +static mp_obj_t network_ppp_connect(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + enum { ARG_security, ARG_user, ARG_key, ARG_authmode, ARG_username, ARG_password }; static const mp_arg_t allowed_args[] = { + { MP_QSTR_security, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_user, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_key, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + // Deprecated arguments for backwards compatibility { MP_QSTR_authmode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = PPPAUTHTYPE_NONE} }, { MP_QSTR_username, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_password, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, @@ -200,17 +294,34 @@ static mp_obj_t ppp_connect_py(size_t n_args, const mp_obj_t *args, mp_map_t *kw mp_arg_val_t parsed_args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args - 1, args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, parsed_args); - ppp_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + // Use deprecated arguments as defaults + if (parsed_args[ARG_security].u_int == -1) { + parsed_args[ARG_security].u_int = parsed_args[ARG_authmode].u_int; + } + if (parsed_args[ARG_user].u_obj == mp_const_none) { + parsed_args[ARG_user].u_obj = parsed_args[ARG_username].u_obj; + } + if (parsed_args[ARG_key].u_obj == mp_const_none) { + parsed_args[ARG_key].u_obj = parsed_args[ARG_password].u_obj; + } + + network_ppp_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (self->state == STATE_INACTIVE) { + self->pcb = pppapi_pppos_create(&self->netif, network_ppp_output_callback, network_ppp_status_cb, self); + if (self->pcb == NULL) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("pppos_create failed")); + } + self->state = STATE_ACTIVE; - if (!self->active) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("must be active")); + network_ppp_stream_uart_irq_enable(self); } - if (self->client_task_handle != NULL) { + if (self->state == STATE_CONNECTING || self->state == STATE_CONNECTED) { mp_raise_OSError(MP_EALREADY); } - switch (parsed_args[ARG_authmode].u_int) { + switch (parsed_args[ARG_security].u_int) { case PPPAUTHTYPE_NONE: case PPPAUTHTYPE_PAP: case PPPAUTHTYPE_CHAP: @@ -219,39 +330,49 @@ static mp_obj_t ppp_connect_py(size_t n_args, const mp_obj_t *args, mp_map_t *kw mp_raise_ValueError(MP_ERROR_TEXT("invalid auth")); } - if (parsed_args[ARG_authmode].u_int != PPPAUTHTYPE_NONE) { - const char *username_str = mp_obj_str_get_str(parsed_args[ARG_username].u_obj); - const char *password_str = mp_obj_str_get_str(parsed_args[ARG_password].u_obj); - pppapi_set_auth(self->pcb, parsed_args[ARG_authmode].u_int, username_str, password_str); + if (parsed_args[ARG_security].u_int != PPPAUTHTYPE_NONE) { + const char *user_str = mp_obj_str_get_str(parsed_args[ARG_user].u_obj); + const char *key_str = mp_obj_str_get_str(parsed_args[ARG_key].u_obj); + pppapi_set_auth(self->pcb, parsed_args[ARG_security].u_int, user_str, key_str); } - if (pppapi_set_default(self->pcb) != ESP_OK) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("set default failed")); + + if (pppapi_set_default(self->pcb) != ERR_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("ppp_set_default failed")); } ppp_set_usepeerdns(self->pcb, true); - if (pppapi_connect(self->pcb, 0) != ESP_OK) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("connect failed")); + if (pppapi_connect(self->pcb, 0) != ERR_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("ppp_connect failed")); } - if (xTaskCreatePinnedToCore(pppos_client_task, "ppp", 2048, self, 1, (TaskHandle_t *)&self->client_task_handle, MP_TASK_COREID) != pdPASS) { - mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("failed to create worker task")); - } + self->state = STATE_CONNECTING; + + // Do a poll in case there is data waiting on the input stream. + network_ppp_poll(1, args); return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_KW(ppp_connect_obj, 1, ppp_connect_py); +static MP_DEFINE_CONST_FUN_OBJ_KW(network_ppp_connect_obj, 1, network_ppp_connect); -static mp_obj_t ppp_delete(mp_obj_t self_in) { - ppp_if_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_obj_t args[] = {self, mp_const_false}; - ppp_active(2, args); +static mp_obj_t network_ppp_disconnect(mp_obj_t self_in) { + network_ppp_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->state == STATE_CONNECTING || self->state == STATE_CONNECTED) { + // Initiate close and wait for PPPERR_USER callback. + pppapi_close(self->pcb, 0); + } return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_1(ppp_delete_obj, ppp_delete); +static MP_DEFINE_CONST_FUN_OBJ_1(network_ppp_disconnect_obj, network_ppp_disconnect); + +static mp_obj_t network_ppp_isconnected(mp_obj_t self_in) { + network_ppp_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bool(self->state == STATE_CONNECTED); +} +static MP_DEFINE_CONST_FUN_OBJ_1(network_ppp_isconnected_obj, network_ppp_isconnected); -static mp_obj_t ppp_ifconfig(size_t n_args, const mp_obj_t *args) { - ppp_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); +static mp_obj_t network_ppp_ifconfig(size_t n_args, const mp_obj_t *args) { + network_ppp_obj_t *self = MP_OBJ_TO_PTR(args[0]); if (n_args == 1) { // get const ip_addr_t *dns; @@ -282,11 +403,11 @@ static mp_obj_t ppp_ifconfig(size_t n_args, const mp_obj_t *args) { return mp_const_none; } } -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ppp_ifconfig_obj, 1, 2, ppp_ifconfig); +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_ppp_ifconfig_obj, 1, 2, network_ppp_ifconfig); -static mp_obj_t ppp_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { +static mp_obj_t network_ppp_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + network_ppp_obj_t *self = MP_OBJ_TO_PTR(args[0]); if (kwargs->used == 0) { - ppp_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); if (self->pcb == NULL) { mp_raise_ValueError(MP_ERROR_TEXT("PPP not active")); } @@ -318,94 +439,64 @@ static mp_obj_t ppp_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwar } return mp_const_none; } -static MP_DEFINE_CONST_FUN_OBJ_KW(ppp_ipconfig_obj, 1, ppp_ipconfig); +static MP_DEFINE_CONST_FUN_OBJ_KW(network_ppp_ipconfig_obj, 1, network_ppp_ipconfig); -static mp_obj_t ppp_status(mp_obj_t self_in) { - return mp_const_none; -} -static MP_DEFINE_CONST_FUN_OBJ_1(ppp_status_obj, ppp_status); - -static mp_obj_t ppp_isconnected(mp_obj_t self_in) { - ppp_if_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_bool(self->connected); -} -static MP_DEFINE_CONST_FUN_OBJ_1(ppp_isconnected_obj, ppp_isconnected); - -static mp_obj_t ppp_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { - if (n_args != 1 && kwargs->used != 0) { - mp_raise_TypeError(MP_ERROR_TEXT("either pos or kw args are allowed")); - } - ppp_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); - - if (kwargs->used != 0) { - for (size_t i = 0; i < kwargs->alloc; i++) { - if (mp_map_slot_is_filled(kwargs, i)) { - switch (mp_obj_str_get_qstr(kwargs->table[i].key)) { - case MP_QSTR_stream: { - if (kwargs->table[i].value != mp_const_none) { - mp_get_stream_raise(kwargs->table[i].value, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); - } - self->stream = kwargs->table[i].value; - break; - } - default: - break; - } +static mp_obj_t network_ppp_active(size_t n_args, const mp_obj_t *args) { + network_ppp_obj_t *self = MP_OBJ_TO_PTR(args[0]); + if (n_args > 1) { + if (mp_obj_is_true(args[1])) { + if (self->state >= STATE_ACTIVE) { + return mp_const_true; } - } - return mp_const_none; - } - if (n_args != 2) { - mp_raise_TypeError(MP_ERROR_TEXT("can query only one param")); - } - - mp_obj_t val = mp_const_none; + self->pcb = pppapi_pppos_create(&self->netif, network_ppp_output_callback, network_ppp_status_cb, self); + if (self->pcb == NULL) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("pppos_create failed")); + } + self->state = STATE_ACTIVE; - switch (mp_obj_str_get_qstr(args[1])) { - case MP_QSTR_stream: { - val = self->stream; - break; - } - case MP_QSTR_ifname: { - if (self->pcb != NULL) { - struct netif *pppif = ppp_netif(self->pcb); - char ifname[NETIF_NAMESIZE + 1] = {0}; - netif_index_to_name(netif_get_index(pppif), ifname); - if (ifname[0] != 0) { - val = mp_obj_new_str_from_cstr((char *)ifname); - } + network_ppp_stream_uart_irq_enable(self); + } else { + if (self->state < STATE_ACTIVE) { + return mp_const_false; } - break; + + network_ppp___del__(MP_OBJ_FROM_PTR(self)); } - default: - mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); } - - return val; + return mp_obj_new_bool(self->state >= STATE_ACTIVE); } -static MP_DEFINE_CONST_FUN_OBJ_KW(ppp_config_obj, 1, ppp_config); - -static const mp_rom_map_elem_t ppp_if_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&ppp_active_obj) }, - { MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&ppp_connect_obj) }, - { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&ppp_isconnected_obj) }, - { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&ppp_status_obj) }, - { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&ppp_config_obj) }, - { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&ppp_ifconfig_obj) }, - { MP_ROM_QSTR(MP_QSTR_ipconfig), MP_ROM_PTR(&ppp_ipconfig_obj) }, - { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&ppp_delete_obj) }, +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_ppp_active_obj, 1, 2, network_ppp_active); + +static const mp_rom_map_elem_t network_ppp_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&network_ppp___del___obj) }, + { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&network_ppp_config_obj) }, + { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&network_ppp_status_obj) }, + { MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&network_ppp_connect_obj) }, + { MP_ROM_QSTR(MP_QSTR_disconnect), MP_ROM_PTR(&network_ppp_disconnect_obj) }, + { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&network_ppp_isconnected_obj) }, + { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&network_ppp_ifconfig_obj) }, + { MP_ROM_QSTR(MP_QSTR_ipconfig), MP_ROM_PTR(&network_ppp_ipconfig_obj) }, + { MP_ROM_QSTR(MP_QSTR_poll), MP_ROM_PTR(&network_ppp_poll_obj) }, + + { MP_ROM_QSTR(MP_QSTR_SEC_NONE), MP_ROM_INT(PPPAUTHTYPE_NONE) }, + { MP_ROM_QSTR(MP_QSTR_SEC_PAP), MP_ROM_INT(PPPAUTHTYPE_PAP) }, + { MP_ROM_QSTR(MP_QSTR_SEC_CHAP), MP_ROM_INT(PPPAUTHTYPE_CHAP) }, + + // Deprecated interface for backwards compatibility + { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&network_ppp_active_obj) }, { MP_ROM_QSTR(MP_QSTR_AUTH_NONE), MP_ROM_INT(PPPAUTHTYPE_NONE) }, { MP_ROM_QSTR(MP_QSTR_AUTH_PAP), MP_ROM_INT(PPPAUTHTYPE_PAP) }, { MP_ROM_QSTR(MP_QSTR_AUTH_CHAP), MP_ROM_INT(PPPAUTHTYPE_CHAP) }, }; -static MP_DEFINE_CONST_DICT(ppp_if_locals_dict, ppp_if_locals_dict_table); +static MP_DEFINE_CONST_DICT(network_ppp_locals_dict, network_ppp_locals_dict_table); MP_DEFINE_CONST_OBJ_TYPE( - ppp_if_type, + esp_network_ppp_lwip_type, MP_QSTR_PPP, MP_TYPE_FLAG_NONE, - locals_dict, &ppp_if_locals_dict + make_new, network_ppp_make_new, + locals_dict, &network_ppp_locals_dict ); #endif From 51b821ce820008b5419bf6889d15d5a7c00a2c99 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 13 May 2025 12:35:49 +1000 Subject: [PATCH 104/154] tools/verifygitlog.py: Disallow a leading slash in commit subject line. Signed-off-by: Damien George --- tools/verifygitlog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/verifygitlog.py b/tools/verifygitlog.py index 5234611983be6..f2d3d722d0883 100755 --- a/tools/verifygitlog.py +++ b/tools/verifygitlog.py @@ -116,8 +116,8 @@ def verify_message_body(raw_body, err): def verify_subject_line_prefix(prefix, err): ext = (".c", ".h", ".cpp", ".js", ".rst", ".md") - if prefix.startswith("."): - err.error('Subject prefix cannot begin with ".".') + if prefix.startswith((".", "/")): + err.error('Subject prefix cannot begin with "." or "/".') if prefix.endswith("/"): err.error('Subject prefix cannot end with "/".') From 420897045171305fa36440c08b575ebe3f6d4ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Wed, 14 May 2025 18:05:06 +0200 Subject: [PATCH 105/154] tools/verifygitlog.py: Allow long co-author and sign-off names. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: DaniĆ«l van de Giessen --- tools/verifygitlog.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/verifygitlog.py b/tools/verifygitlog.py index f2d3d722d0883..dba6ebd6de59a 100755 --- a/tools/verifygitlog.py +++ b/tools/verifygitlog.py @@ -105,8 +105,12 @@ def verify_message_body(raw_body, err): # Message body lines. for line in raw_body[2:]: - # Long lines with URLs are exempt from the line length rule. - if len(line) >= 76 and "://" not in line: + # Long lines with URLs or human names are exempt from the line length rule. + if len(line) >= 76 and not ( + "://" in line + or line.startswith("Co-authored-by: ") + or line.startswith("Signed-off-by: ") + ): err.error("Message lines should be 75 or less characters: " + line) if not raw_body[-1].startswith("Signed-off-by: ") or "@" not in raw_body[-1]: From 62d26bfc15840b6bc172a081066931b2fce6a696 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 13 May 2025 12:54:47 +1000 Subject: [PATCH 106/154] extmod/vfs_lfsx: Fix errno value raised from chdir. OSError errno values should be positive. Signed-off-by: Damien George --- extmod/vfs_lfsx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extmod/vfs_lfsx.c b/extmod/vfs_lfsx.c index 4b10ca3aa597c..404eab84f47a8 100644 --- a/extmod/vfs_lfsx.c +++ b/extmod/vfs_lfsx.c @@ -300,7 +300,7 @@ static mp_obj_t MP_VFS_LFSx(chdir)(mp_obj_t self_in, mp_obj_t path_in) { struct LFSx_API (info) info; int ret = LFSx_API(stat)(&self->lfs, path, &info); if (ret < 0 || info.type != LFSx_MACRO(_TYPE_DIR)) { - mp_raise_OSError(-MP_ENOENT); + mp_raise_OSError(MP_ENOENT); } } From 44bcfe53de6fdc1f3eb0f104798f5679d7b4473d Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 13 May 2025 12:55:01 +1000 Subject: [PATCH 107/154] tests/extmod/vfs_lfs_error.py: Test value of all OSError's errno. To make sure they have the correct value. Signed-off-by: Damien George --- tests/extmod/vfs_lfs_error.py | 46 +++++++++++++++---------------- tests/extmod/vfs_lfs_error.py.exp | 44 ++++++++++++++--------------- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/tests/extmod/vfs_lfs_error.py b/tests/extmod/vfs_lfs_error.py index 2ac7629bfa8fc..73cdf3437330b 100644 --- a/tests/extmod/vfs_lfs_error.py +++ b/tests/extmod/vfs_lfs_error.py @@ -1,7 +1,7 @@ # Test for VfsLittle using a RAM device, testing error handling try: - import vfs + import errno, vfs vfs.VfsLfs1 vfs.VfsLfs2 @@ -41,14 +41,14 @@ def test(bdev, vfs_class): # mkfs with too-small block device try: vfs_class.mkfs(RAMBlockDevice(1)) - except OSError: - print("mkfs OSError") + except OSError as er: + print("mkfs OSError", er.errno > 0) # mount with invalid filesystem try: vfs_class(bdev) - except OSError: - print("mount OSError") + except OSError as er: + print("mount OSError", er.errno > 0) # set up for following tests vfs_class.mkfs(bdev) @@ -60,60 +60,60 @@ def test(bdev, vfs_class): # ilistdir try: fs.ilistdir("noexist") - except OSError: - print("ilistdir OSError") + except OSError as er: + print("ilistdir OSError", er) # remove try: fs.remove("noexist") - except OSError: - print("remove OSError") + except OSError as er: + print("remove OSError", er) # rmdir try: fs.rmdir("noexist") - except OSError: - print("rmdir OSError") + except OSError as er: + print("rmdir OSError", er) # rename try: fs.rename("noexist", "somethingelse") - except OSError: - print("rename OSError") + except OSError as er: + print("rename OSError", er) # mkdir try: fs.mkdir("testdir") - except OSError: - print("mkdir OSError") + except OSError as er: + print("mkdir OSError", er) # chdir to nonexistent try: fs.chdir("noexist") - except OSError: - print("chdir OSError") + except OSError as er: + print("chdir OSError", er) print(fs.getcwd()) # check still at root # chdir to file try: fs.chdir("testfile") - except OSError: - print("chdir OSError") + except OSError as er: + print("chdir OSError", er) print(fs.getcwd()) # check still at root # stat try: fs.stat("noexist") - except OSError: - print("stat OSError") + except OSError as er: + print("stat OSError", er) # error during seek with fs.open("testfile", "r") as f: f.seek(1 << 30) # SEEK_SET try: f.seek(1 << 30, 1) # SEEK_CUR - except OSError: - print("seek OSError") + except OSError as er: + print("seek OSError", er) bdev = RAMBlockDevice(30) diff --git a/tests/extmod/vfs_lfs_error.py.exp b/tests/extmod/vfs_lfs_error.py.exp index f4327f6962ec3..440607ed84b22 100644 --- a/tests/extmod/vfs_lfs_error.py.exp +++ b/tests/extmod/vfs_lfs_error.py.exp @@ -1,28 +1,28 @@ test -mkfs OSError -mount OSError -ilistdir OSError -remove OSError -rmdir OSError -rename OSError -mkdir OSError -chdir OSError +mkfs OSError True +mount OSError True +ilistdir OSError [Errno 2] ENOENT +remove OSError [Errno 2] ENOENT +rmdir OSError [Errno 2] ENOENT +rename OSError [Errno 2] ENOENT +mkdir OSError [Errno 17] EEXIST +chdir OSError [Errno 2] ENOENT / -chdir OSError +chdir OSError [Errno 2] ENOENT / -stat OSError -seek OSError +stat OSError [Errno 2] ENOENT +seek OSError [Errno 22] EINVAL test -mkfs OSError -mount OSError -ilistdir OSError -remove OSError -rmdir OSError -rename OSError -mkdir OSError -chdir OSError +mkfs OSError True +mount OSError True +ilistdir OSError [Errno 2] ENOENT +remove OSError [Errno 2] ENOENT +rmdir OSError [Errno 2] ENOENT +rename OSError [Errno 2] ENOENT +mkdir OSError [Errno 17] EEXIST +chdir OSError [Errno 2] ENOENT / -chdir OSError +chdir OSError [Errno 2] ENOENT / -stat OSError -seek OSError +stat OSError [Errno 2] ENOENT +seek OSError [Errno 22] EINVAL From e22c666d0623e6273d748f787a84d3108a57f1c3 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 9 May 2025 19:17:50 +0200 Subject: [PATCH 108/154] tests/cpydiff: Explain the numeric literal parsing difference. Fixes issue #17224. Signed-off-by: Jeff Epler --- tests/cpydiff/syntax_spaces.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/cpydiff/syntax_spaces.py b/tests/cpydiff/syntax_spaces.py index 03d25d56199d1..86faa5981ceb0 100644 --- a/tests/cpydiff/syntax_spaces.py +++ b/tests/cpydiff/syntax_spaces.py @@ -1,8 +1,15 @@ """ categories: Syntax,Spaces -description: uPy requires spaces between literal numbers and keywords, CPy doesn't -cause: Unknown -workaround: Unknown +description: MicroPython requires spaces between literal numbers and keywords, CPython doesn't +cause: Different parser implementation + +MicroPython's tokenizer treats a sequence like ``1and`` as a single token, while CPython treats it as two tokens. + +Since CPython 3.11, this syntax causes a ``SyntaxWarning`` for an "invalid literal". + +workaround: Add a space between the integer literal and the intended next token. + +This also fixes the ``SyntaxWarning`` in CPython. """ try: From 2f97d1dd28a7e2d34df268676e533944410aab42 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 9 May 2025 21:22:33 +0200 Subject: [PATCH 109/154] tests/cpydiff: Document that uPy requires space after number+period. Signed-off-by: Jeff Epler --- tests/cpydiff/syntax_spaces.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/cpydiff/syntax_spaces.py b/tests/cpydiff/syntax_spaces.py index 86faa5981ceb0..e7d00838cb667 100644 --- a/tests/cpydiff/syntax_spaces.py +++ b/tests/cpydiff/syntax_spaces.py @@ -1,11 +1,11 @@ """ categories: Syntax,Spaces -description: MicroPython requires spaces between literal numbers and keywords, CPython doesn't +description: MicroPython requires spaces between literal numbers and keywords or ".", CPython doesn't cause: Different parser implementation MicroPython's tokenizer treats a sequence like ``1and`` as a single token, while CPython treats it as two tokens. -Since CPython 3.11, this syntax causes a ``SyntaxWarning`` for an "invalid literal". +Since CPython 3.11, when the literal number is followed by a token, this syntax causes a ``SyntaxWarning`` for an "invalid literal". When a literal number is followed by a "." denoting attribute access, CPython does not warn. workaround: Add a space between the integer literal and the intended next token. @@ -24,3 +24,7 @@ print(eval("1if 1else 0")) except SyntaxError: print("Should have worked") +try: + print(eval("0x1.to_bytes(1)")) +except SyntaxError: + print("Should have worked") From 605eda158de4f40ba03e15fa183a178f7cf1a1da Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 9 May 2025 21:26:26 +0200 Subject: [PATCH 110/154] tools/gen-cpydiff.py: Improve stdout vs stderr interleaving. In the syntax_space cpydiff, all the warnings were shown after the other output. This is because the output always showed all of stdout first and all of stdout second. By running Python in unbuffered mode and using `stderr=STDOUT`, the two streams are interleaved in exactly the order they're printed, so the SyntaxWarnings are interleaved with the other output. By using the `encoding=` argument of Popen, the need to explicitly convert to utf-8 is avoided. The encoding of the input also becomes utf-8 in this case, which all the test cases are (well, they're all ASCII, I think). As in `run-tests.py`, setting PYTHONIOENCODING ensures the Python interpreter's input and output are in utf-8, which is not always the case, especially on Windows systems. I spot-checked the generated doc pages and they all seemed to make sense still. Signed-off-by: Jeff Epler --- tools/gen-cpydiff.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/tools/gen-cpydiff.py b/tools/gen-cpydiff.py index 2f9394deeae00..744cde8d7b57b 100644 --- a/tools/gen-cpydiff.py +++ b/tools/gen-cpydiff.py @@ -45,6 +45,12 @@ CPYTHON3 = os.getenv("MICROPY_CPYTHON3", "python3") MICROPYTHON = os.getenv("MICROPY_MICROPYTHON", "../ports/unix/build-standard/micropython") +# Set PYTHONIOENCODING so that CPython will use utf-8 on systems which set another encoding in the locale +os.environ["PYTHONIOENCODING"] = "utf-8" + +# Set PYTHONUNBUFFERED so that CPython will interleave stdout & stderr without buffering +os.environ["PYTHONUNBUFFERED"] = "a non-empty string" + TESTPATH = "../tests/cpydiff" DOCPATH = "../docs/genrst" SRCDIR = "../docs/differences" @@ -111,7 +117,7 @@ def run_tests(tests): results = [] for test in tests: test_fullpath = os.path.join(TESTPATH, test.name) - with open(test_fullpath, "rb") as f: + with open(test_fullpath, "r") as f: input_py = f.read() process = subprocess.Popen( @@ -119,20 +125,22 @@ def run_tests(tests): shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, - stderr=subprocess.PIPE, + stderr=subprocess.STDOUT, + encoding="utf-8", ) - output_cpy = [com.decode("utf8") for com in process.communicate(input_py)] + output_cpy = process.communicate(input_py)[0] process = subprocess.Popen( MICROPYTHON, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, - stderr=subprocess.PIPE, + stderr=subprocess.STDOUT, + encoding="utf-8", ) - output_upy = [com.decode("utf8") for com in process.communicate(input_py)] + output_upy = process.communicate(input_py)[0] - if output_cpy[0] == output_upy[0] and output_cpy[1] == output_upy[1]: + if output_cpy == output_upy: print("Error: Test has same output in CPython vs MicroPython: " + test_fullpath) same_results = True else: @@ -246,9 +254,9 @@ def gen_rst(results): rst.write("**Workaround:** " + output.workaround + "\n\n") rst.write("Sample code::\n\n" + indent(output.code, TAB) + "\n") - output_cpy = indent("".join(output.output_cpy[0:2]), TAB).rstrip() + output_cpy = indent(output.output_cpy, TAB).rstrip() output_cpy = ("::\n\n" if output_cpy != "" else "") + output_cpy - output_upy = indent("".join(output.output_upy[0:2]), TAB).rstrip() + output_upy = indent(output.output_upy, TAB).rstrip() output_upy = ("::\n\n" if output_upy != "" else "") + output_upy table = gen_table([["CPy output:", output_cpy], ["uPy output:", output_upy]]) rst.write(table) From 8648e6d1cf19afec1485ed204b2135ca322c6a8b Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 9 May 2025 22:04:29 +0200 Subject: [PATCH 111/154] tests/cpydiff: Add test of underscore-in-literals. Signed-off-by: Jeff Epler --- tests/cpydiff/syntax_literal_underscore.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/cpydiff/syntax_literal_underscore.py diff --git a/tests/cpydiff/syntax_literal_underscore.py b/tests/cpydiff/syntax_literal_underscore.py new file mode 100644 index 0000000000000..4b1406e9f3f71 --- /dev/null +++ b/tests/cpydiff/syntax_literal_underscore.py @@ -0,0 +1,19 @@ +""" +categories: Syntax,Literals +description: MicroPython accepts underscores in numeric literals where CPython doesn't +cause: Different parser implementation + +MicroPython's tokenizer ignores underscores in numeric literals, while CPython +rejects multiple consecutive underscores and underscores after the last digit. + +workaround: Remove the underscores not accepted by CPython. +""" + +try: + print(eval("1__1")) +except SyntaxError: + print("Should not work") +try: + print(eval("1_")) +except SyntaxError: + print("Should not work") From a19d3f742e84eb72fd8fb2a0f4e38822392ea88e Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 9 May 2025 22:08:23 +0200 Subject: [PATCH 112/154] tools/gen-cpydiff.py: Fix RST heading generation. The heading character for the difference title was always "~", but items had been added which had just a single heading level. This made the generated table of contents confused about heading levels, because heading levels are not fixed in rst, but are inferred from the order they appear in the document. Signed-off-by: Jeff Epler --- tools/gen-cpydiff.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/gen-cpydiff.py b/tools/gen-cpydiff.py index 744cde8d7b57b..278023a4bddcd 100644 --- a/tools/gen-cpydiff.py +++ b/tools/gen-cpydiff.py @@ -228,8 +228,8 @@ def gen_rst(results): filename = section[i].replace(" ", "_").lower() rst = open(os.path.join(DOCPATH, filename + ".rst"), "w") rst.write(HEADER) - rst.write(section[i] + "\n") - rst.write(RSTCHARS[0] * len(section[i])) + rst.write(section[0] + "\n") + rst.write(RSTCHARS[0] * len(section[0]) + "\n\n") rst.write(time.strftime("\nGenerated %a %d %b %Y %X UTC\n\n", time.gmtime())) # If a file docs/differences/_preamble.txt exists # then its output is inserted after the top-level heading, @@ -247,7 +247,7 @@ def gen_rst(results): class_ = section rst.write(".. _cpydiff_%s:\n\n" % os.path.splitext(output.name)[0]) rst.write(output.desc + "\n") - rst.write("~" * len(output.desc) + "\n\n") + rst.write(RSTCHARS[min(i + 1, len(RSTCHARS) - 1)] * len(output.desc) + "\n\n") if output.cause != "Unknown": rst.write("**Cause:** " + output.cause + "\n\n") if output.workaround != "Unknown": From ea19f3b7358eba713d6bcea5d5fd141312cf2e9d Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 9 May 2025 22:08:35 +0200 Subject: [PATCH 113/154] tests/cpydiff: Ensure all have two levels of category. This improves the TOC display of the generated differences section. Signed-off-by: Jeff Epler --- tests/cpydiff/core_fstring_concat.py | 2 +- tests/cpydiff/core_fstring_parser.py | 2 +- tests/cpydiff/core_fstring_repr.py | 2 +- tests/cpydiff/syntax_arg_unpacking.py | 2 +- tests/cpydiff/syntax_spaces.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/cpydiff/core_fstring_concat.py b/tests/cpydiff/core_fstring_concat.py index 3daa13d75360e..2fbe1b961a1e6 100644 --- a/tests/cpydiff/core_fstring_concat.py +++ b/tests/cpydiff/core_fstring_concat.py @@ -1,5 +1,5 @@ """ -categories: Core +categories: Core,f-strings description: f-strings don't support concatenation with adjacent literals if the adjacent literals contain braces cause: MicroPython is optimised for code space. workaround: Use the + operator between literal strings when they are not both f-strings diff --git a/tests/cpydiff/core_fstring_parser.py b/tests/cpydiff/core_fstring_parser.py index 22bbc5866ec3a..570b92434a9a0 100644 --- a/tests/cpydiff/core_fstring_parser.py +++ b/tests/cpydiff/core_fstring_parser.py @@ -1,5 +1,5 @@ """ -categories: Core +categories: Core,f-strings description: f-strings cannot support expressions that require parsing to resolve unbalanced nested braces and brackets cause: MicroPython is optimised for code space. workaround: Always use balanced braces and brackets in expressions inside f-strings diff --git a/tests/cpydiff/core_fstring_repr.py b/tests/cpydiff/core_fstring_repr.py index d37fb48db758c..2589a34b7e3b9 100644 --- a/tests/cpydiff/core_fstring_repr.py +++ b/tests/cpydiff/core_fstring_repr.py @@ -1,5 +1,5 @@ """ -categories: Core +categories: Core,f-strings description: f-strings don't support !a conversions cause: MicropPython does not implement ascii() workaround: None diff --git a/tests/cpydiff/syntax_arg_unpacking.py b/tests/cpydiff/syntax_arg_unpacking.py index e54832ddb9165..7133a8a28272c 100644 --- a/tests/cpydiff/syntax_arg_unpacking.py +++ b/tests/cpydiff/syntax_arg_unpacking.py @@ -1,5 +1,5 @@ """ -categories: Syntax +categories: Syntax,Unpacking description: Argument unpacking does not work if the argument being unpacked is the nth or greater argument where n is the number of bits in an MP_SMALL_INT. cause: The implementation uses an MP_SMALL_INT to flag args that need to be unpacked. workaround: Use fewer arguments. diff --git a/tests/cpydiff/syntax_spaces.py b/tests/cpydiff/syntax_spaces.py index e7d00838cb667..670cefdeac23e 100644 --- a/tests/cpydiff/syntax_spaces.py +++ b/tests/cpydiff/syntax_spaces.py @@ -1,5 +1,5 @@ """ -categories: Syntax,Spaces +categories: Syntax,Literals description: MicroPython requires spaces between literal numbers and keywords or ".", CPython doesn't cause: Different parser implementation From 6d74b4e3c15d0b677247ce02c459095d3cf03fc5 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 10 May 2025 08:31:27 +0200 Subject: [PATCH 114/154] tools/gen-cpydiff.py: Ensure every item has at least 2 TOC levels. Previously, the navigation ended up messy when the (long) description of the item became used as a 2nd level header, meaning that it was placed in the navigation. Check for this when generating cpydiff so that new cases don't sneak in unnoticed. Signed-off-by: Jeff Epler --- tools/gen-cpydiff.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/gen-cpydiff.py b/tools/gen-cpydiff.py index 278023a4bddcd..3bb928090bb6e 100644 --- a/tools/gen-cpydiff.py +++ b/tools/gen-cpydiff.py @@ -219,6 +219,8 @@ def gen_rst(results): class_ = [] for output in results: section = output.class_.split(",") + if len(section) < 2: + raise SystemExit("Each item must have at least 2 categories") for i in range(len(section)): section[i] = section[i].rstrip() if section[i] in CLASSMAP: From c310301f27e351e0dc9f966556cab512b3c82615 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Mon, 16 Dec 2024 14:44:37 +0200 Subject: [PATCH 115/154] docs/esp32: Improve PWM documentation and examples. This reduces inconsitencies between esp32 and other ports. According to the discussion in #10817. Signed-off-by: Ihor Nehrutsa --- docs/esp32/quickref.rst | 35 ++++-- docs/esp32/tutorial/pwm.rst | 220 ++++++++++++++++++++++++++++------- docs/library/machine.PWM.rst | 14 +-- 3 files changed, 209 insertions(+), 60 deletions(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index ccc01099d17f9..707ae27c08cf2 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -393,7 +393,7 @@ Use the :ref:`machine.PWM ` class:: pwm0.duty(256) # set duty cycle from 0 to 1023 as a ratio duty/1023, (now 25%) duty_u16 = pwm0.duty_u16() # get current duty cycle, range 0-65535 - pwm0.duty_u16(2**16*3//4) # set duty cycle from 0 to 65535 as a ratio duty_u16/65535, (now 75%) + pwm0.duty_u16(65536*3//4) # set duty cycle from 0 to 65535 as a ratio duty_u16/65535, (now 75%) duty_ns = pwm0.duty_ns() # get current pulse width in ns pwm0.duty_ns(250_000) # set pulse width in nanoseconds from 0 to 1_000_000_000/freq, (now 25%) @@ -402,19 +402,32 @@ Use the :ref:`machine.PWM ` class:: pwm2 = PWM(Pin(2), freq=20000, duty=512) # create and configure in one go print(pwm2) # view PWM settings + pwm2.deinit() # turn off PWM on the pin + + pwm0 = PWM(Pin(0), duty_u16=16384) # The output is at a high level 25% of the time. + pwm2 = PWM(Pin(2), duty_u16=16384, invert=1) # The output is at a low level 25% of the time. + + pwm4 = PWM(Pin(4), lightsleep=True) # Allow PWM during light sleep mode ESP chips have different hardware peripherals: -===================================================== ======== ======== ======== -Hardware specification ESP32 ESP32-S2 ESP32-C3 ------------------------------------------------------ -------- -------- -------- -Number of groups (speed modes) 2 1 1 -Number of timers per group 4 4 4 -Number of channels per group 8 8 6 ------------------------------------------------------ -------- -------- -------- -Different PWM frequencies (groups * timers) 8 4 4 -Total PWM channels (Pins, duties) (groups * channels) 16 8 6 -===================================================== ======== ======== ======== +======================================================= ======== ========= ========== +Hardware specification ESP32 ESP32-S2, ESP32-C2, + ESP32-S3, ESP32-C3, + ESP32-P4 ESP32-C5, + ESP32-C6, + ESP32-H2 +------------------------------------------------------- -------- --------- ---------- +Number of groups (speed modes) 2 1 1 +Number of timers per group 4 4 4 +Number of channels per group 8 8 6 +------------------------------------------------------- -------- --------- ---------- +Different PWM frequencies = (groups * timers) 8 4 4 +Total PWM channels (Pins, duties) = (groups * channels) 16 8 6 +======================================================= ======== ========= ========== + +In light sleep, the ESP32 PWM can only operate in low speed mode, so only 4 timers and +8 channels are available. A maximum number of PWM channels (Pins) are available on the ESP32 - 16 channels, but only 8 different PWM frequencies are available, the remaining 8 channels must diff --git a/docs/esp32/tutorial/pwm.rst b/docs/esp32/tutorial/pwm.rst index 2650284d35f41..82d43b36f6cb6 100644 --- a/docs/esp32/tutorial/pwm.rst +++ b/docs/esp32/tutorial/pwm.rst @@ -11,16 +11,20 @@ compared with the length of a single period (low plus high time). Maximum duty cycle is when the pin is high all of the time, and minimum is when it is low all of the time. -* More comprehensive example with all 16 PWM channels and 8 timers:: +* More comprehensive example with all **16 PWM channels and 8 timers**:: + from time import sleep from machine import Pin, PWM try: - f = 100 # Hz - d = 1024 // 16 # 6.25% - pins = (15, 2, 4, 16, 18, 19, 22, 23, 25, 26, 27, 14 , 12, 13, 32, 33) + F = 10000 # Hz + D = 65536 // 16 # 6.25% + pins = (2, 4, 12, 13, 14, 15, 16, 18, 19, 22, 23, 25, 26, 27, 32, 33) pwms = [] for i, pin in enumerate(pins): - pwms.append(PWM(Pin(pin), freq=f * (i // 2 + 1), duty= 1023 if i==15 else d * (i + 1))) + f = F * (i // 2 + 1) + d = min(65535, D * (i + 1)) + pwms.append(PWM(pin, freq=f, duty_u16=d)) + sleep(2 / f) print(pwms[i]) finally: for pwm in pwms: @@ -31,65 +35,100 @@ low all of the time. Output is:: - PWM(Pin(15), freq=100, duty=64, resolution=10, mode=0, channel=0, timer=0) - PWM(Pin(2), freq=100, duty=128, resolution=10, mode=0, channel=1, timer=0) - PWM(Pin(4), freq=200, duty=192, resolution=10, mode=0, channel=2, timer=1) - PWM(Pin(16), freq=200, duty=256, resolution=10, mode=0, channel=3, timer=1) - PWM(Pin(18), freq=300, duty=320, resolution=10, mode=0, channel=4, timer=2) - PWM(Pin(19), freq=300, duty=384, resolution=10, mode=0, channel=5, timer=2) - PWM(Pin(22), freq=400, duty=448, resolution=10, mode=0, channel=6, timer=3) - PWM(Pin(23), freq=400, duty=512, resolution=10, mode=0, channel=7, timer=3) - PWM(Pin(25), freq=500, duty=576, resolution=10, mode=1, channel=0, timer=0) - PWM(Pin(26), freq=500, duty=640, resolution=10, mode=1, channel=1, timer=0) - PWM(Pin(27), freq=600, duty=704, resolution=10, mode=1, channel=2, timer=1) - PWM(Pin(14), freq=600, duty=768, resolution=10, mode=1, channel=3, timer=1) - PWM(Pin(12), freq=700, duty=832, resolution=10, mode=1, channel=4, timer=2) - PWM(Pin(13), freq=700, duty=896, resolution=10, mode=1, channel=5, timer=2) - PWM(Pin(32), freq=800, duty=960, resolution=10, mode=1, channel=6, timer=3) - PWM(Pin(33), freq=800, duty=1023, resolution=10, mode=1, channel=7, timer=3) - -* Example of a smooth frequency change:: + PWM(Pin(2), freq=10000, duty_u16=4096) + PWM(Pin(4), freq=10000, duty_u16=8192) + PWM(Pin(12), freq=20000, duty_u16=12288) + PWM(Pin(13), freq=20000, duty_u16=16384) + PWM(Pin(14), freq=30030, duty_u16=20480) + PWM(Pin(15), freq=30030, duty_u16=24576) + PWM(Pin(16), freq=40000, duty_u16=28672) + PWM(Pin(18), freq=40000, duty_u16=32768) + PWM(Pin(19), freq=50000, duty_u16=36864) + PWM(Pin(22), freq=50000, duty_u16=40960) + PWM(Pin(23), freq=60060, duty_u16=45056) + PWM(Pin(25), freq=60060, duty_u16=49152) + PWM(Pin(26), freq=69930, duty_u16=53248) + PWM(Pin(27), freq=69930, duty_u16=57344) + PWM(Pin(32), freq=80000, duty_u16=61440) + PWM(Pin(33), freq=80000, duty_u16=65535) + + +* Example of a **smooth frequency change**:: from time import sleep from machine import Pin, PWM - F_MIN = 500 - F_MAX = 1000 + F_MIN = 1000 + F_MAX = 10000 f = F_MIN - delta_f = 1 + delta_f = F_MAX // 50 - p = PWM(Pin(5), f) - print(p) + pwm = PWM(Pin(27), f) while True: - p.freq(f) - - sleep(10 / F_MIN) + pwm.freq(f) + sleep(1 / f) + sleep(0.1) + print(pwm) f += delta_f - if f >= F_MAX or f <= F_MIN: + if f > F_MAX or f < F_MIN: delta_f = -delta_f + print() + if f > F_MAX: + f = F_MAX + elif f < F_MIN: + f = F_MIN - See PWM wave at Pin(5) with an oscilloscope. + See PWM wave on Pin(27) with an oscilloscope. + + Output is:: -* Example of a smooth duty change:: + PWM(Pin(27), freq=998, duty_u16=32768) + PWM(Pin(27), freq=1202, duty_u16=32768) + PWM(Pin(27), freq=1401, duty_u16=32768) + PWM(Pin(27), freq=1598, duty_u16=32768) + ... + PWM(Pin(27), freq=9398, duty_u16=32768) + PWM(Pin(27), freq=9615, duty_u16=32768) + PWM(Pin(27), freq=9804, duty_u16=32768) + PWM(Pin(27), freq=10000, duty_u16=32768) + + PWM(Pin(27), freq=10000, duty_u16=32768) + PWM(Pin(27), freq=9804, duty_u16=32768) + PWM(Pin(27), freq=9615, duty_u16=32768) + PWM(Pin(27), freq=9398, duty_u16=32768) + ... + PWM(Pin(27), freq=1598, duty_u16=32768) + PWM(Pin(27), freq=1401, duty_u16=32768) + PWM(Pin(27), freq=1202, duty_u16=32768) + PWM(Pin(27), freq=998, duty_u16=32768) + + +* Example of a **smooth duty change**:: from time import sleep from machine import Pin, PWM - DUTY_MAX = 2**16 - 1 + DUTY_MAX = 65535 duty_u16 = 0 - delta_d = 16 + delta_d = 256 - p = PWM(Pin(5), 1000, duty_u16=duty_u16) - print(p) + pwm = PWM(Pin(27), freq=1000, duty_u16=duty_u16) while True: - p.duty_u16(duty_u16) + pwm.duty_u16(duty_u16) + sleep(2 / pwm.freq()) + print(pwm) - sleep(1 / 1000) + if duty_u16 >= DUTY_MAX: + print() + sleep(2) + elif duty_u16 <= 0: + print() + sleep(2) duty_u16 += delta_d if duty_u16 >= DUTY_MAX: @@ -99,9 +138,106 @@ low all of the time. duty_u16 = 0 delta_d = -delta_d - See PWM wave at Pin(5) with an oscilloscope. + PWM wave on Pin(27) with an oscilloscope. + + Output is:: + + PWM(Pin(27), freq=998, duty_u16=0) + PWM(Pin(27), freq=998, duty_u16=256) + PWM(Pin(27), freq=998, duty_u16=512) + PWM(Pin(27), freq=998, duty_u16=768) + PWM(Pin(27), freq=998, duty_u16=1024) + ... + PWM(Pin(27), freq=998, duty_u16=64512) + PWM(Pin(27), freq=998, duty_u16=64768) + PWM(Pin(27), freq=998, duty_u16=65024) + PWM(Pin(27), freq=998, duty_u16=65280) + PWM(Pin(27), freq=998, duty_u16=65535) + + PWM(Pin(27), freq=998, duty_u16=65279) + PWM(Pin(27), freq=998, duty_u16=65023) + PWM(Pin(27), freq=998, duty_u16=64767) + PWM(Pin(27), freq=998, duty_u16=64511) + ... + PWM(Pin(27), freq=998, duty_u16=1023) + PWM(Pin(27), freq=998, duty_u16=767) + PWM(Pin(27), freq=998, duty_u16=511) + PWM(Pin(27), freq=998, duty_u16=255) + PWM(Pin(27), freq=998, duty_u16=0) + + +* Example of a **smooth duty change and PWM output inversion**:: + + from utime import sleep + from machine import Pin, PWM + + try: + DUTY_MAX = 65535 + + duty_u16 = 0 + delta_d = 65536 // 32 + + pwm = PWM(Pin(27)) + pwmi = PWM(Pin(32), invert=1) + + while True: + pwm.duty_u16(duty_u16) + pwmi.duty_u16(duty_u16) + + duty_u16 += delta_d + if duty_u16 >= DUTY_MAX: + duty_u16 = DUTY_MAX + delta_d = -delta_d + elif duty_u16 <= 0: + duty_u16 = 0 + delta_d = -delta_d + + sleep(.01) + print(pwm) + print(pwmi) + + finally: + try: + pwm.deinit() + except: + pass + try: + pwmi.deinit() + except: + pass + + Output is:: + + PWM(Pin(27), freq=5000, duty_u16=0) + PWM(Pin(32), freq=5000, duty_u16=32768, invert=1) + PWM(Pin(27), freq=5000, duty_u16=2048) + PWM(Pin(32), freq=5000, duty_u16=2048, invert=1) + PWM(Pin(27), freq=5000, duty_u16=4096) + PWM(Pin(32), freq=5000, duty_u16=4096, invert=1) + PWM(Pin(27), freq=5000, duty_u16=6144) + PWM(Pin(32), freq=5000, duty_u16=6144, invert=1) + PWM(Pin(27), freq=5000, duty_u16=8192) + PWM(Pin(32), freq=5000, duty_u16=8192, invert=1) + ... + + + See PWM waves on Pin(27) and Pin(32) with an oscilloscope. + +Note: New PWM parameters take effect in the next PWM cycle. + + pwm = PWM(2, duty=512) + print(pwm) + >>> PWM(Pin(2), freq=5000, duty=1023) # the duty is not relevant + pwm.init(freq=2, duty=64) + print(pwm) + >>> PWM(Pin(2), freq=2, duty=16) # the duty is not relevant + time.sleep(1 / 2) # wait one PWM period + print(pwm) + >>> PWM(Pin(2), freq=2, duty=64) # the duty is actual + +Note: machine.freq(20_000_000) reduces the highest PWM frequency to 10 MHz. -Note: the Pin.OUT mode does not need to be specified. The channel is initialized +Note: the Pin.OUT mode does not need to be specified. The channel is initialized to PWM mode internally once for each Pin that is passed to the PWM constructor. The following code is wrong:: diff --git a/docs/library/machine.PWM.rst b/docs/library/machine.PWM.rst index 5f592b8dff593..c2b606affd675 100644 --- a/docs/library/machine.PWM.rst +++ b/docs/library/machine.PWM.rst @@ -11,20 +11,20 @@ Example usage:: from machine import PWM pwm = PWM(pin, freq=50, duty_u16=8192) # create a PWM object on a pin - # and set freq and duty - pwm.duty_u16(32768) # set duty to 50% + # and set freq 50 Hz and duty 12.5% + pwm.duty_u16(32768) # set duty to 50% # reinitialise with a period of 200us, duty of 5us pwm.init(freq=5000, duty_ns=5000) - pwm.duty_ns(3000) # set pulse width to 3us + pwm.duty_ns(3000) # set pulse width to 3us pwm.deinit() Constructors ------------ -.. class:: PWM(dest, *, freq, duty_u16, duty_ns, invert) +.. class:: PWM(dest, *, freq, duty_u16, duty_ns, invert=False) Construct and return a new PWM object using the following parameters: @@ -40,7 +40,7 @@ Constructors Setting *freq* may affect other PWM objects if the objects share the same underlying PWM generator (this is hardware specific). Only one of *duty_u16* and *duty_ns* should be specified at a time. - *invert* is not available at all ports. + *invert* is available only on the esp32, mimxrt, nrf, rp2, samd and zephyr ports. Methods ------- @@ -116,10 +116,10 @@ Limitations of PWM resolution of 8 bit, not 16-bit as may be expected. In this case, the lowest 8 bits of *duty_u16* are insignificant. So:: - pwm=PWM(Pin(13), freq=300_000, duty_u16=2**16//2) + pwm=PWM(Pin(13), freq=300_000, duty_u16=65536//2) and:: - pwm=PWM(Pin(13), freq=300_000, duty_u16=2**16//2 + 255) + pwm=PWM(Pin(13), freq=300_000, duty_u16=65536//2 + 255) will generate PWM with the same 50% duty cycle. From 150a5aa3a102fb0f1f0388275e7e59d669bb79e5 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Wed, 19 Feb 2025 09:21:01 +0200 Subject: [PATCH 116/154] esp32/machine_pwm: Improve PWM and make its API match other ports. This reduce inconsistencies between esp32 PWM and other ports: 1. duty_u16() high value is 2**16-1 == 65535 2. Invert PWM wave with invert=1 parameter 3. Enable PWM in light sleep mode 4. Allow PWM output and read pulse input simultaneously on the same Pin() 5. Code refactoring Co-Authored-By: Angus Gratton Co-Authored-By: robert-hh Co-Authored-By: Andrew Leech Co-Authored-By: Yoann Darche Signed-off-by: Ihor Nehrutsa --- ports/esp32/machine_pwm.c | 974 +++++++++++++++++++++----------------- 1 file changed, 538 insertions(+), 436 deletions(-) diff --git a/ports/esp32/machine_pwm.c b/ports/esp32/machine_pwm.c index 7826769556c98..7cb84640eecaf 100644 --- a/ports/esp32/machine_pwm.c +++ b/ports/esp32/machine_pwm.c @@ -6,7 +6,8 @@ * Copyright (c) 2016-2021 Damien P. George * Copyright (c) 2018 Alan Dragomirecky * Copyright (c) 2020 Antoine Aubert - * Copyright (c) 2021 Ihor Nehrutsa + * Copyright (c) 2021, 2023-2025 Ihor Nehrutsa + * Copyright (c) 2024 Yoann Darche * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -32,79 +33,41 @@ #include #include "py/mphal.h" -#include "driver/ledc.h" #include "esp_err.h" +#include "driver/ledc.h" +#include "soc/ledc_periph.h" #include "soc/gpio_sig_map.h" - -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) #include "esp_clk_tree.h" -#endif - -#define PWM_DBG(...) -// #define PWM_DBG(...) mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, "\n"); - -// Total number of channels -#define PWM_CHANNEL_MAX (LEDC_SPEED_MODE_MAX * LEDC_CHANNEL_MAX) - -typedef struct _chan_t { - // Which channel has which GPIO pin assigned? - // (-1 if not assigned) - gpio_num_t pin; - // Which channel has which timer assigned? - // (-1 if not assigned) - int timer_idx; -} chan_t; +#include "py/mpprint.h" -// List of PWM channels -static chan_t chans[PWM_CHANNEL_MAX]; +#define debug_printf(...) mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, " | %d at %s\n", __LINE__, __FILE__); -// channel_idx is an index (end-to-end sequential numbering) for all channels -// available on the chip and described in chans[] -#define CHANNEL_IDX(mode, channel) (mode * LEDC_CHANNEL_MAX + channel) -#define CHANNEL_IDX_TO_MODE(channel_idx) (channel_idx / LEDC_CHANNEL_MAX) -#define CHANNEL_IDX_TO_CHANNEL(channel_idx) (channel_idx % LEDC_CHANNEL_MAX) +#define FADE 0 -// Total number of timers -#define PWM_TIMER_MAX (LEDC_SPEED_MODE_MAX * LEDC_TIMER_MAX) +// 10-bit user interface resolution compatible with esp8266 PWM.duty() +#define UI_RES_10_BIT (10) +#define DUTY_10 UI_RES_10_BIT +// Maximum duty value on 10-bit resolution is 1024 but reduced to 1023 in UI +#define MAX_10_DUTY (1U << UI_RES_10_BIT) -// List of timer configs -static ledc_timer_config_t timers[PWM_TIMER_MAX]; +// 16-bit user interface resolution used in PWM.duty_u16() +#define UI_RES_16_BIT (16) +#define DUTY_16 UI_RES_16_BIT +// Maximum duty value on 16-bit resolution is 65536 but reduced to 65535 in UI +#define MAX_16_DUTY (1U << UI_RES_16_BIT) -// timer_idx is an index (end-to-end sequential numbering) for all timers -// available on the chip and configured in timers[] -#define TIMER_IDX(mode, timer) (mode * LEDC_TIMER_MAX + timer) -#define TIMER_IDX_TO_MODE(timer_idx) (timer_idx / LEDC_TIMER_MAX) -#define TIMER_IDX_TO_TIMER(timer_idx) (timer_idx % LEDC_TIMER_MAX) +// ns user interface used in PWM.duty_ns() +#define DUTY_NS (1) -// Params for PWM operation // 5khz is default frequency -#define PWM_FREQ (5000) - -// 10-bit resolution (compatible with esp8266 PWM) -#define PWM_RES_10_BIT (LEDC_TIMER_10_BIT) - -// Maximum duty value on 10-bit resolution -#define MAX_DUTY_U10 ((1 << PWM_RES_10_BIT) - 1) -// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/ledc.html#supported-range-of-frequency-and-duty-resolutions -// duty() uses 10-bit resolution or less -// duty_u16() and duty_ns() use 16-bit resolution or less - -// Possible highest resolution in device -#if (LEDC_TIMER_BIT_MAX - 1) < LEDC_TIMER_16_BIT -#define HIGHEST_PWM_RES (LEDC_TIMER_BIT_MAX - 1) -#else -#define HIGHEST_PWM_RES (LEDC_TIMER_16_BIT) // 20 bit for ESP32, but 16 bit is used -#endif -// Duty resolution of user interface in `duty_u16()` and `duty_u16` parameter in constructor/initializer -#define UI_RES_16_BIT (16) -// Maximum duty value on highest user interface resolution -#define UI_MAX_DUTY ((1 << UI_RES_16_BIT) - 1) -// How much to shift from the HIGHEST_PWM_RES duty resolution to the user interface duty resolution UI_RES_16_BIT -#define UI_RES_SHIFT (UI_RES_16_BIT - HIGHEST_PWM_RES) // 0 for ESP32, 2 for S2, S3, C3 - -#if SOC_LEDC_SUPPORT_REF_TICK +#define PWM_FREQ (5000) +// default duty 50% +#define PWM_DUTY ((1U << UI_RES_16_BIT) / 2) + +// All chips except esp32 and esp32s2 do not have timer-specific clock sources, which means clock source for all timers must be the same one. +#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 // If the PWM frequency is less than EMPIRIC_FREQ, then LEDC_REF_CLK_HZ(1 MHz) source is used, else LEDC_APB_CLK_HZ(80 MHz) source is used -#define EMPIRIC_FREQ (10) // Hz +#define EMPIRIC_FREQ (10) // Hz #endif // Config of timer upon which we run all PWM'ed GPIO pins @@ -113,490 +76,675 @@ static bool pwm_inited = false; // MicroPython PWM object struct typedef struct _machine_pwm_obj_t { mp_obj_base_t base; - gpio_num_t pin; - bool active; - int mode; - int channel; - int timer; - int duty_x; // PWM_RES_10_BIT if duty(), HIGHEST_PWM_RES if duty_u16(), -HIGHEST_PWM_RES if duty_ns() - int duty_u10; // stored values from previous duty setters - int duty_u16; // - / - - int duty_ns; // - / - + int8_t pin; + int8_t mode; + int8_t channel; + int8_t timer; + bool lightsleep; + int32_t freq; + int8_t duty_scale; // DUTY_10 if duty(), DUTY_16 if duty_u16(), DUTY_NS if duty_ns() + int duty_ui; // saved values of UI duty + int channel_duty; // saved values of UI duty, calculated to raw channel->duty + bool output_invert; + bool output_invert_prev; } machine_pwm_obj_t; -static bool is_timer_in_use(int current_channel_idx, int timer_idx); -static void set_duty_u16(machine_pwm_obj_t *self, int duty); -static void set_duty_u10(machine_pwm_obj_t *self, int duty); -static void set_duty_ns(machine_pwm_obj_t *self, int ns); +typedef struct _chans_t { + int8_t pin; // Which channel has which GPIO pin assigned? (-1 if not assigned) + int8_t timer; // Which channel has which timer assigned? (-1 if not assigned) + bool lightsleep; // Is light sleep enable has been set for this pin +} chans_t; + +// List of PWM channels +static chans_t chans[LEDC_SPEED_MODE_MAX][LEDC_CHANNEL_MAX]; + +typedef struct _timers_t { + int32_t freq; + int8_t duty_resolution; + ledc_clk_cfg_t clk_cfg; +} timers_t; + +// List of PWM timers +static timers_t timers[LEDC_SPEED_MODE_MAX][LEDC_TIMER_MAX]; + +// register-unregister channel +static void register_channel(int mode, int channel, int pin, int timer) { + chans[mode][channel].pin = pin; + chans[mode][channel].timer = timer; +} + +static void unregister_channel(int mode, int channel) { + register_channel(mode, channel, -1, -1); + chans[mode][channel].lightsleep = false; +} + +static void unregister_timer(int mode, int timer) { + timers[mode][timer].freq = -1; // unused timer freq is -1 + timers[mode][timer].duty_resolution = 0; + timers[mode][timer].clk_cfg = LEDC_AUTO_CLK; +} static void pwm_init(void) { - // Initial condition: no channels assigned - for (int i = 0; i < PWM_CHANNEL_MAX; ++i) { - chans[i].pin = -1; - chans[i].timer_idx = -1; + for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { + // Initial condition: no channels assigned + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + unregister_channel(mode, channel); + } + // Initial condition: no timers assigned + for (int timer = 0; timer < LEDC_TIMER_MAX; ++timer) { + unregister_timer(mode, timer); + } } +} - // Prepare all timers config - // Initial condition: no timers assigned - for (int i = 0; i < PWM_TIMER_MAX; ++i) { - timers[i].duty_resolution = HIGHEST_PWM_RES; - // unset timer is -1 - timers[i].freq_hz = -1; - timers[i].speed_mode = TIMER_IDX_TO_MODE(i); - timers[i].timer_num = TIMER_IDX_TO_TIMER(i); - timers[i].clk_cfg = LEDC_AUTO_CLK; // will reinstall later according to the EMPIRIC_FREQ +// Returns true if the timer is in use in addition to current channel +static bool is_timer_in_use(int mode, int current_channel, int timer) { + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + if ((channel != current_channel) && (chans[mode][channel].timer == timer) && (chans[mode][channel].pin >= 0)) { + return true; + } } + return false; } // Deinit channel and timer if the timer is unused -static void pwm_deinit(int channel_idx) { - // Valid channel? - if ((channel_idx >= 0) && (channel_idx < PWM_CHANNEL_MAX)) { +static void pwm_deinit(int mode, int channel, int level) { + // Is valid channel? + if ((mode >= 0) && (mode < LEDC_SPEED_MODE_MAX) && (channel >= 0) && (channel < LEDC_CHANNEL_MAX)) { // Clean up timer if necessary - int timer_idx = chans[channel_idx].timer_idx; - if (timer_idx != -1) { - if (!is_timer_in_use(channel_idx, timer_idx)) { - check_esp_err(ledc_timer_pause(TIMER_IDX_TO_MODE(timer_idx), TIMER_IDX_TO_TIMER(timer_idx))); - ledc_timer_config_t timer_config = { + int timer = chans[mode][channel].timer; + if (timer >= 0) { + if (!is_timer_in_use(mode, channel, timer)) { + check_esp_err(ledc_timer_pause(mode, timer)); + ledc_timer_config_t ledc_timer = { .deconfigure = true, - .speed_mode = TIMER_IDX_TO_MODE(timer_idx), - .timer_num = TIMER_IDX_TO_TIMER(timer_idx), + .speed_mode = mode, + .timer_num = timer, }; - check_esp_err(ledc_timer_config(&timer_config)); - // Flag it unused - timers[chans[channel_idx].timer_idx].freq_hz = -1; + ledc_timer_config(&ledc_timer); + unregister_timer(mode, timer); } } - int pin = chans[channel_idx].pin; - if (pin != -1) { - int mode = CHANNEL_IDX_TO_MODE(channel_idx); - int channel = CHANNEL_IDX_TO_CHANNEL(channel_idx); - // Mark it unused, and tell the hardware to stop routing - check_esp_err(ledc_stop(mode, channel, 0)); - // Disable ledc signal for the pin - // esp_rom_gpio_connect_out_signal(pin, SIG_GPIO_OUT_IDX, false, false); - if (mode == LEDC_LOW_SPEED_MODE) { - esp_rom_gpio_connect_out_signal(pin, LEDC_LS_SIG_OUT0_IDX + channel, false, true); - } else { - #if LEDC_SPEED_MODE_MAX > 1 - #if CONFIG_IDF_TARGET_ESP32 - esp_rom_gpio_connect_out_signal(pin, LEDC_HS_SIG_OUT0_IDX + channel, false, true); - #else - #error Add supported CONFIG_IDF_TARGET_ESP32_xxx - #endif - #endif + int pin = chans[mode][channel].pin; + if (pin >= 0) { + // Disable LEDC output, and set idle level + check_esp_err(ledc_stop(mode, channel, level)); + if (chans[mode][channel].lightsleep) { + // Enable SLP_SEL to change GPIO status automantically in lightsleep. + check_esp_err(gpio_sleep_sel_en(pin)); + chans[mode][channel].lightsleep = false; } } - chans[channel_idx].pin = -1; - chans[channel_idx].timer_idx = -1; + unregister_channel(mode, channel); } } // This called from Ctrl-D soft reboot void machine_pwm_deinit_all(void) { if (pwm_inited) { - for (int channel_idx = 0; channel_idx < PWM_CHANNEL_MAX; ++channel_idx) { - pwm_deinit(channel_idx); + for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + pwm_deinit(mode, channel, 0); + } } + #if FADE + ledc_fade_func_uninstall(); + #endif pwm_inited = false; } } -static void configure_channel(machine_pwm_obj_t *self) { - ledc_channel_config_t cfg = { - .channel = self->channel, - .duty = (1 << (timers[TIMER_IDX(self->mode, self->timer)].duty_resolution)) / 2, - .gpio_num = self->pin, - .intr_type = LEDC_INTR_DISABLE, - .speed_mode = self->mode, - .timer_sel = self->timer, - }; - if (ledc_channel_config(&cfg) != ESP_OK) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM not supported on Pin(%d)"), self->pin); - } -} - -static void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_config_t *timer) { - esp_err_t err; - if (freq != timer->freq_hz) { - // Configure the new frequency and resolution - timer->freq_hz = freq; - - #if SOC_LEDC_SUPPORT_PLL_DIV_CLOCK - timer->clk_cfg = LEDC_USE_PLL_DIV_CLK; - #elif SOC_LEDC_SUPPORT_APB_CLOCK - timer->clk_cfg = LEDC_USE_APB_CLK; - #elif SOC_LEDC_SUPPORT_XTAL_CLOCK - timer->clk_cfg = LEDC_USE_XTAL_CLK; - #else - #error No supported PWM / LEDC clocks. - #endif - #if SOC_LEDC_SUPPORT_REF_TICK - if (freq < EMPIRIC_FREQ) { - timer->clk_cfg = LEDC_USE_REF_TICK; - } - #endif - uint32_t src_clk_freq = 0; - err = esp_clk_tree_src_get_freq_hz(timer->clk_cfg, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_freq); - if (err != ESP_OK) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unable to query source clock frequency %d"), (int)timer->clk_cfg); - } - - timer->duty_resolution = ledc_find_suitable_duty_resolution(src_clk_freq, timer->freq_hz); - - // Set frequency - err = ledc_timer_config(timer); - if (err != ESP_OK) { - if (err == ESP_FAIL) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unreachable frequency %d"), freq); - } else { - check_esp_err(err); - } - } - // Reset the timer if low speed - if (self->mode == LEDC_LOW_SPEED_MODE) { - check_esp_err(ledc_timer_rst(self->mode, self->timer)); - } - } - - // Save the same duty cycle when frequency is changed - if (self->duty_x == HIGHEST_PWM_RES) { - set_duty_u16(self, self->duty_u16); - } else if (self->duty_x == PWM_RES_10_BIT) { - set_duty_u10(self, self->duty_u10); - } else if (self->duty_x == -HIGHEST_PWM_RES) { - set_duty_ns(self, self->duty_ns); +static void pwm_is_active(machine_pwm_obj_t *self) { + if (self->timer < 0) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("PWM is inactive")); } } // Calculate the duty parameters based on an ns value static int ns_to_duty(machine_pwm_obj_t *self, int ns) { - ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)]; - int64_t duty = ((int64_t)ns * UI_MAX_DUTY * timer.freq_hz + 500000000LL) / 1000000000LL; + pwm_is_active(self); + int64_t duty = ((int64_t)ns * (int64_t)MAX_16_DUTY * self->freq + 500000000LL) / 1000000000LL; if ((ns > 0) && (duty == 0)) { duty = 1; - } else if (duty > UI_MAX_DUTY) { - duty = UI_MAX_DUTY; + } else if (duty > MAX_16_DUTY) { + duty = MAX_16_DUTY; } return duty; } static int duty_to_ns(machine_pwm_obj_t *self, int duty) { - ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)]; - int64_t ns = ((int64_t)duty * 1000000000LL + (int64_t)timer.freq_hz * UI_MAX_DUTY / 2) / ((int64_t)timer.freq_hz * UI_MAX_DUTY); - return ns; + pwm_is_active(self); + return ((int64_t)duty * 1000000000LL + (int64_t)self->freq * (int64_t)(MAX_16_DUTY / 2)) / ((int64_t)self->freq * (int64_t)MAX_16_DUTY); } -#define get_duty_raw(self) ledc_get_duty(self->mode, self->channel) +// Reconfigure PWM pin output as input/output. +static void reconfigure_pin(machine_pwm_obj_t *self) { + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 0) + // This allows to read the pin level. + gpio_set_direction(self->pin, GPIO_MODE_INPUT_OUTPUT); + #endif + esp_rom_gpio_connect_out_signal(self->pin, ledc_periph_signal[self->mode].sig_out0_idx + self->channel, self->output_invert, 0); +} -static void pwm_is_active(machine_pwm_obj_t *self) { - if (self->active == false) { - mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("PWM inactive")); +static void apply_duty(machine_pwm_obj_t *self) { + pwm_is_active(self); + + int duty = 0; + if (self->duty_scale == DUTY_16) { + duty = self->duty_ui; + } else if (self->duty_scale == DUTY_10) { + duty = self->duty_ui << (UI_RES_16_BIT - UI_RES_10_BIT); + } else if (self->duty_scale == DUTY_NS) { + duty = ns_to_duty(self, self->duty_ui); + } + self->channel_duty = duty >> (UI_RES_16_BIT - timers[self->mode][self->timer].duty_resolution); + if ((chans[self->mode][self->channel].pin == -1) || (self->output_invert_prev != self->output_invert)) { + self->output_invert_prev = self->output_invert; + // New PWM assignment + ledc_channel_config_t cfg = { + .channel = self->channel, + .duty = self->channel_duty, + .gpio_num = self->pin, + .intr_type = LEDC_INTR_DISABLE, + .speed_mode = self->mode, + .timer_sel = self->timer, + .hpoint = 0, + .flags.output_invert = self->output_invert, + }; + check_esp_err(ledc_channel_config(&cfg)); + reconfigure_pin(self); + } else { + #if FADE + check_esp_err(ledc_set_duty_and_update(self->mode, self->channel, self->channel_duty, 0)); + #else + check_esp_err(ledc_set_duty(self->mode, self->channel, self->channel_duty)); + check_esp_err(ledc_update_duty(self->mode, self->channel)); + #endif + } + if (self->lightsleep) { + // Disable SLP_SEL to change GPIO status automantically in lightsleep. + check_esp_err(gpio_sleep_sel_dis(self->pin)); + chans[self->mode][self->channel].lightsleep = true; } + register_channel(self->mode, self->channel, self->pin, self->timer); +} + +static uint32_t find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq) { + unsigned int resolution = ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq); + if (resolution > UI_RES_16_BIT) { + // limit resolution to user interface + resolution = UI_RES_16_BIT; + } + // Note: On ESP32, ESP32S2, ESP32S3, ESP32C3, ESP32C2, ESP32C6, ESP32H2, ESP32P4, due to a hardware bug, + // 100% duty cycle (i.e. 2**duty_res) is not reachable when the binded timer selects the maximum duty + // resolution. For example, the max duty resolution on ESP32C3 is 14-bit width, then set duty to (2**14) + // will mess up the duty calculation in hardware. + // Reduce the resolution from 14 to 13 bits to resolve the hardware bug. + if (resolution >= SOC_LEDC_TIMER_BIT_WIDTH) { + resolution -= 1; + } + return resolution; } static uint32_t get_duty_u16(machine_pwm_obj_t *self) { pwm_is_active(self); - int resolution = timers[TIMER_IDX(self->mode, self->timer)].duty_resolution; - int duty = ledc_get_duty(self->mode, self->channel); - if (resolution <= UI_RES_16_BIT) { - duty <<= (UI_RES_16_BIT - resolution); + int duty = ledc_get_duty(self->mode, self->channel) << (UI_RES_16_BIT - timers[self->mode][self->timer].duty_resolution); + if (duty != MAX_16_DUTY) { + return duty; } else { - duty >>= (resolution - UI_RES_16_BIT); + return MAX_16_DUTY - 1; } - return duty; } static uint32_t get_duty_u10(machine_pwm_obj_t *self) { - pwm_is_active(self); - return get_duty_u16(self) >> 6; // Scale down from 16 bit to 10 bit resolution + // Scale down from 16 bit to 10 bit resolution + return get_duty_u16(self) >> (UI_RES_16_BIT - UI_RES_10_BIT); } static uint32_t get_duty_ns(machine_pwm_obj_t *self) { - pwm_is_active(self); return duty_to_ns(self, get_duty_u16(self)); } -static void set_duty_u16(machine_pwm_obj_t *self, int duty) { - pwm_is_active(self); - if ((duty < 0) || (duty > UI_MAX_DUTY)) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_u16 must be from 0 to %d"), UI_MAX_DUTY); +static void check_duty_u16(machine_pwm_obj_t *self, int duty) { + if ((duty < 0) || (duty > MAX_16_DUTY - 1)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_u16 must be from 0 to %d"), MAX_16_DUTY); } - ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)]; - int channel_duty; - if (timer.duty_resolution <= UI_RES_16_BIT) { - channel_duty = duty >> (UI_RES_16_BIT - timer.duty_resolution); - } else { - channel_duty = duty << (timer.duty_resolution - UI_RES_16_BIT); + if (duty == MAX_16_DUTY - 1) { + duty = MAX_16_DUTY; } - int max_duty = (1 << timer.duty_resolution) - 1; - if (channel_duty < 0) { - channel_duty = 0; - } else if (channel_duty > max_duty) { - channel_duty = max_duty; - } - check_esp_err(ledc_set_duty(self->mode, self->channel, channel_duty)); - check_esp_err(ledc_update_duty(self->mode, self->channel)); + self->duty_scale = DUTY_16; + self->duty_ui = duty; +} - /* - // Bug: Sometimes duty is not set right now. - // Not a bug. It's a feature. The duty is applied at the beginning of the next signal period. - // Bug: It has been experimentally established that the duty is set during 2 signal periods, but 1 period is expected. - // See https://github.com/espressif/esp-idf/issues/7288 - if (duty != get_duty_u16(self)) { - PWM_DBG("set_duty_u16(%u), get_duty_u16():%u, channel_duty:%d, duty_resolution:%d, freq_hz:%d", duty, get_duty_u16(self), channel_duty, timer.duty_resolution, timer.freq_hz); - esp_rom_delay_us(2 * 1000000 / timer.freq_hz); - if (duty != get_duty_u16(self)) { - PWM_DBG("set_duty_u16(%u), get_duty_u16():%u, channel_duty:%d, duty_resolution:%d, freq_hz:%d", duty, get_duty_u16(self), channel_duty, timer.duty_resolution, timer.freq_hz); - } - } - */ +static void set_duty_u16(machine_pwm_obj_t *self, int duty) { + check_duty_u16(self, duty); + apply_duty(self); +} - self->duty_x = HIGHEST_PWM_RES; - self->duty_u16 = duty; +static void check_duty_u10(machine_pwm_obj_t *self, int duty) { + if ((duty < 0) || (duty > MAX_10_DUTY - 1)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty must be from 0 to %u"), MAX_10_DUTY - 1); + } + if (duty == MAX_10_DUTY - 1) { + duty = MAX_10_DUTY; + } + self->duty_scale = DUTY_10; + self->duty_ui = duty; } static void set_duty_u10(machine_pwm_obj_t *self, int duty) { - pwm_is_active(self); - if ((duty < 0) || (duty > MAX_DUTY_U10)) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty must be from 0 to %u"), MAX_DUTY_U10); + check_duty_u10(self, duty); + apply_duty(self); +} + +static void check_duty_ns(machine_pwm_obj_t *self, int ns) { + if ((ns < 0) || (ns > duty_to_ns(self, MAX_16_DUTY))) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_ns must be from 0 to %d ns"), duty_to_ns(self, MAX_16_DUTY)); } - set_duty_u16(self, duty << (UI_RES_16_BIT - PWM_RES_10_BIT)); - self->duty_x = PWM_RES_10_BIT; - self->duty_u10 = duty; + self->duty_scale = DUTY_NS; + self->duty_ui = ns; } static void set_duty_ns(machine_pwm_obj_t *self, int ns) { - pwm_is_active(self); - if ((ns < 0) || (ns > duty_to_ns(self, UI_MAX_DUTY))) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_ns must be from 0 to %d ns"), duty_to_ns(self, UI_MAX_DUTY)); + check_duty_ns(self, ns); + apply_duty(self); +} + +#if !(CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2) +// This check if a clock is already set in the timer list, if yes, return the LEDC_XXX value +static ledc_clk_cfg_t find_clock_in_use() { + for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { + for (int timer = 0; timer < LEDC_TIMER_MAX; ++timer) { + if (timers[mode][timer].clk_cfg != LEDC_AUTO_CLK) { + return timers[mode][timer].clk_cfg; + } + } } - set_duty_u16(self, ns_to_duty(self, ns)); - self->duty_x = -HIGHEST_PWM_RES; - self->duty_ns = ns; + return LEDC_AUTO_CLK; } -/******************************************************************************/ +// Check if a timer is already set with a different clock source +static bool is_timer_with_different_clock(int mode, int current_timer, ledc_clk_cfg_t clk_cfg) { + for (int timer = 0; timer < LEDC_TIMER_MAX; ++timer) { + if ((timer != current_timer) && (clk_cfg != LEDC_AUTO_CLK) && (timers[mode][timer].clk_cfg != LEDC_AUTO_CLK) && (timers[mode][timer].clk_cfg != clk_cfg)) { + return true; + } + } + return false; +} +#endif -#define SAME_FREQ_ONLY (true) -#define SAME_FREQ_OR_FREE (false) -#define ANY_MODE (-1) +static void check_freq_ranges(machine_pwm_obj_t *self, int freq, int upper_freq) { + if ((freq <= 0) || (freq > upper_freq)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("frequency must be from 1Hz to %dMHz"), upper_freq / 1000000); + } +} -// Return timer_idx. Use TIMER_IDX_TO_MODE(timer_idx) and TIMER_IDX_TO_TIMER(timer_idx) to get mode and timer -static int find_timer(unsigned int freq, bool same_freq_only, int mode) { - int free_timer_idx_found = -1; - // Find a free PWM Timer using the same freq - for (int timer_idx = 0; timer_idx < PWM_TIMER_MAX; ++timer_idx) { - if ((mode == ANY_MODE) || (mode == TIMER_IDX_TO_MODE(timer_idx))) { - if (timers[timer_idx].freq_hz == freq) { - // A timer already uses the same freq. Use it now. - return timer_idx; - } - if (!same_freq_only && (free_timer_idx_found == -1) && (timers[timer_idx].freq_hz == -1)) { - free_timer_idx_found = timer_idx; - // Continue to check if a channel with the same freq is in use. +// Set timer frequency +static void set_freq(machine_pwm_obj_t *self, unsigned int freq) { + self->freq = freq; + if ((timers[self->mode][self->timer].freq != freq) || (self->lightsleep)) { + ledc_timer_config_t timer = {}; + timer.speed_mode = self->mode; + timer.timer_num = self->timer; + timer.freq_hz = freq; + timer.deconfigure = false; + + timer.clk_cfg = LEDC_AUTO_CLK; + #if !(CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2) + ledc_clk_cfg_t clk_cfg = find_clock_in_use(); + if (clk_cfg != LEDC_AUTO_CLK) { + timer.clk_cfg = clk_cfg; + } else + #endif + if (self->lightsleep) { + timer.clk_cfg = LEDC_USE_RC_FAST_CLK; // 8 or 20 MHz + } else { + #if SOC_LEDC_SUPPORT_APB_CLOCK + timer.clk_cfg = LEDC_USE_APB_CLK; // 80 MHz + #elif SOC_LEDC_SUPPORT_PLL_DIV_CLOCK + timer.clk_cfg = LEDC_USE_PLL_DIV_CLK; // 60 or 80 or 96 MHz + #elif SOC_LEDC_SUPPORT_XTAL_CLOCK + timer.clk_cfg = LEDC_USE_XTAL_CLK; // 40 MHz + #else + #error No supported PWM / LEDC clocks. + #endif + + #ifdef EMPIRIC_FREQ // ESP32 and ESP32S2 only + if (freq < EMPIRIC_FREQ) { + timer.clk_cfg = LEDC_USE_REF_TICK; // 1 MHz } + #endif } - } + #if !(CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2) + // Check for clock source conflict + clk_cfg = find_clock_in_use(); + if ((clk_cfg != LEDC_AUTO_CLK) && (clk_cfg != timer.clk_cfg)) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("one or more active timers use a different clock source, not supported by the current SoC.")); + } + #endif - return free_timer_idx_found; + uint32_t src_clk_freq = 0; + check_esp_err(esp_clk_tree_src_get_freq_hz(timer.clk_cfg, ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &src_clk_freq)); + // machine.freq(20_000_000) reduces APB_CLK_FREQ to 20MHz and the highest PWM frequency to 10MHz + check_freq_ranges(self, freq, src_clk_freq / 2); + + // Configure the new resolution + timer.duty_resolution = find_suitable_duty_resolution(src_clk_freq, self->freq); + // Configure timer - Set frequency + if ((timers[self->mode][self->timer].duty_resolution == timer.duty_resolution) && (timers[self->mode][self->timer].clk_cfg == timer.clk_cfg)) { + check_esp_err(ledc_set_freq(self->mode, self->timer, freq)); + } else { + check_esp_err(ledc_timer_config(&timer)); + } + // Reset the timer if low speed + if (self->mode == LEDC_LOW_SPEED_MODE) { + check_esp_err(ledc_timer_rst(self->mode, self->timer)); + } + timers[self->mode][self->timer].freq = freq; + timers[self->mode][self->timer].duty_resolution = timer.duty_resolution; + timers[self->mode][self->timer].clk_cfg = timer.clk_cfg; + } } -// Return true if the timer is in use in addition to current channel -static bool is_timer_in_use(int current_channel_idx, int timer_idx) { - for (int i = 0; i < PWM_CHANNEL_MAX; ++i) { - if ((i != current_channel_idx) && (chans[i].timer_idx == timer_idx)) { +static bool is_free_channels(int mode, int pin) { + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + if ((chans[mode][channel].pin < 0) || (chans[mode][channel].pin == pin)) { return true; } } + return false; +} +static bool is_free_timers(int mode, int32_t freq) { + for (int timer = 0; timer < LEDC_TIMER_MAX; ++timer) { + if ((timers[mode][timer].freq < 0) || (timers[mode][timer].freq == freq)) { + return true; + } + } return false; } -// Find a free PWM channel, also spot if our pin is already mentioned. -// Return channel_idx. Use CHANNEL_IDX_TO_MODE(channel_idx) and CHANNEL_IDX_TO_CHANNEL(channel_idx) to get mode and channel -static int find_channel(int pin, int mode) { - int avail_idx = -1; - int channel_idx; - for (channel_idx = 0; channel_idx < PWM_CHANNEL_MAX; ++channel_idx) { - if ((mode == ANY_MODE) || (mode == CHANNEL_IDX_TO_MODE(channel_idx))) { - if (chans[channel_idx].pin == pin) { - break; +// Find self channel or free channel +static void find_channel(machine_pwm_obj_t *self, int *ret_mode, int *ret_channel, int32_t freq) { + // Try to find self channel first + for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { + #if SOC_LEDC_SUPPORT_HS_MODE + if (self->lightsleep && (mode == LEDC_HIGH_SPEED_MODE)) { + continue; + } + #endif + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + if (chans[mode][channel].pin == self->pin) { + *ret_mode = mode; + *ret_channel = channel; + return; } - if ((avail_idx == -1) && (chans[channel_idx].pin == -1)) { - avail_idx = channel_idx; + } + } + // Find free channel + for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { + #if SOC_LEDC_SUPPORT_HS_MODE + if (self->lightsleep && (mode == LEDC_HIGH_SPEED_MODE)) { + continue; + } + #endif + for (int channel = 0; channel < LEDC_CHANNEL_MAX; ++channel) { + if ((chans[mode][channel].pin < 0) && is_free_timers(mode, freq)) { + *ret_mode = mode; + *ret_channel = channel; + return; } } } - if (channel_idx >= PWM_CHANNEL_MAX) { - channel_idx = avail_idx; +} + +// Returns timer with the same frequency, freq == -1 means free timer +static void find_timer(machine_pwm_obj_t *self, int freq, int *ret_mode, int *ret_timer) { + for (int mode = 0; mode < LEDC_SPEED_MODE_MAX; ++mode) { + #if SOC_LEDC_SUPPORT_HS_MODE + if (self->lightsleep && (mode == LEDC_HIGH_SPEED_MODE)) { + continue; + } + #endif + if (is_free_channels(mode, self->pin)) { + for (int timer = 0; timer < LEDC_TIMER_MAX; ++timer) { + if (timers[mode][timer].freq == freq) { + *ret_mode = mode; + *ret_timer = timer; + return; + } + } + } } - return channel_idx; } -/******************************************************************************/ +// Try to find a timer with the same frequency in the current mode, otherwise in the next mode. +// If no existing timer and channel was found, then try to find free timer in any mode. +// If the mode or channel is changed, release the channel and register a new channel in the next mode. +static void select_timer(machine_pwm_obj_t *self, int freq) { + // mode, channel, timer may be -1(not defined) or actual values + int mode = -1; + int timer = -1; + // Check if an already running timer with the required frequency is running + find_timer(self, freq, &mode, &timer); + if (timer < 0) { + // Try to reuse self timer + if ((self->mode >= 0) && (self->channel >= 0)) { + if (!is_timer_in_use(self->mode, self->channel, self->timer)) { + mode = self->mode; + timer = self->timer; + } + } + // If no existing timer and channel was found, then try to find free timer in any mode + if (timer < 0) { + find_timer(self, -1, &mode, &timer); + } + } + if (timer < 0) { + mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("out of %sPWM timers:%d"), self->lightsleep ? "light sleep capable " : "", self->lightsleep ? LEDC_TIMER_MAX : LEDC_SPEED_MODE_MAX *LEDC_TIMER_MAX); + } + // If the timer is found, then register + if (self->timer != timer) { + unregister_channel(self->mode, self->channel); + // Rregister the channel to the timer + self->mode = mode; + self->timer = timer; + register_channel(self->mode, self->channel, -1, self->timer); + } + #if !(CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2) + if (is_timer_with_different_clock(self->mode, self->timer, timers[self->mode][self->timer].clk_cfg)) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("one or more active timers use a different clock source, not supported by the current SoC.")); + } + #endif +} + +static void set_freq_duty(machine_pwm_obj_t *self, unsigned int freq) { + select_timer(self, freq); + set_freq(self, freq); + apply_duty(self); +} + +// ****************************************************************************** // MicroPython bindings for PWM static void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in); mp_printf(print, "PWM(Pin(%u)", self->pin); - if (self->active) { + if (self->timer >= 0) { mp_printf(print, ", freq=%u", ledc_get_freq(self->mode, self->timer)); - - if (self->duty_x == PWM_RES_10_BIT) { + if (self->duty_scale == DUTY_10) { mp_printf(print, ", duty=%d", get_duty_u10(self)); - } else if (self->duty_x == -HIGHEST_PWM_RES) { + } else if (self->duty_scale == DUTY_NS) { mp_printf(print, ", duty_ns=%d", get_duty_ns(self)); } else { mp_printf(print, ", duty_u16=%d", get_duty_u16(self)); } - int resolution = timers[TIMER_IDX(self->mode, self->timer)].duty_resolution; - mp_printf(print, ", resolution=%d", resolution); - - mp_printf(print, ", (duty=%.2f%%, resolution=%.3f%%)", 100.0 * get_duty_raw(self) / (1 << resolution), 100.0 * 1 / (1 << resolution)); // percents - - mp_printf(print, ", mode=%d, channel=%d, timer=%d", self->mode, self->channel, self->timer); + if (self->output_invert) { + mp_printf(print, ", invert=True"); + } + if (self->lightsleep) { + mp_printf(print, ", lightsleep=True"); + } + mp_printf(print, ")"); + + #if MICROPY_ERROR_REPORTING > MICROPY_ERROR_REPORTING_NORMAL + mp_printf(print, " # duty=%.2f%%", 100.0 * get_duty_u16(self) / MAX_16_DUTY); + mp_printf(print, ", raw_duty=%d, resolution=%d", ledc_get_duty(self->mode, self->channel), timers[self->mode][self->timer].duty_resolution); + mp_printf(print, ", mode=%d, timer=%d, channel=%d", self->mode, self->timer, self->channel); + int clk_cfg = timers[self->mode][self->timer].clk_cfg; + mp_printf(print, ", clk_cfg=%d=", clk_cfg); + if (clk_cfg == LEDC_USE_RC_FAST_CLK) { + mp_printf(print, "RC_FAST_CLK"); + } + #if SOC_LEDC_SUPPORT_APB_CLOCK + else if (clk_cfg == LEDC_USE_APB_CLK) { + mp_printf(print, "APB_CLK"); + } + #endif + #if SOC_LEDC_SUPPORT_XTAL_CLOCK + else if (clk_cfg == LEDC_USE_XTAL_CLK) { + mp_printf(print, "XTAL_CLK"); + } + #endif + #if SOC_LEDC_SUPPORT_REF_TICK + else if (clk_cfg == LEDC_USE_REF_TICK) { + mp_printf(print, "REF_TICK"); + } + #endif + #if SOC_LEDC_SUPPORT_PLL_DIV_CLOCK + else if (clk_cfg == LEDC_USE_PLL_DIV_CLK) { + mp_printf(print, "PLL_CLK"); + } + #endif + else if (clk_cfg == LEDC_AUTO_CLK) { + mp_printf(print, "AUTO_CLK"); + } else { + mp_printf(print, "UNKNOWN"); + } + #endif + } else { + mp_printf(print, ")"); } - mp_printf(print, ")"); } // This called from pwm.init() method +// +// Check the current mode. +// If the frequency is changed, try to find a timer with the same frequency +// in the current mode, otherwise in the new mode. +// If the mode is changed, release the channel and select a new channel in the new mode. +// Then set the frequency with the same duty. static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns }; - static const mp_arg_t allowed_args[] = { + + enum { ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns, ARG_invert, ARG_lightsleep }; + mp_arg_t allowed_args[] = { { MP_QSTR_freq, MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_duty, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_duty_u16, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_duty_ns, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_invert, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = self->output_invert} }, + { MP_QSTR_lightsleep, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = self->lightsleep} }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - int channel_idx = find_channel(self->pin, ANY_MODE); - if (channel_idx == -1) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM channels:%d"), PWM_CHANNEL_MAX); // in all modes + self->lightsleep = args[ARG_lightsleep].u_bool; + + int freq = args[ARG_freq].u_int; + if (freq != -1) { + check_freq_ranges(self, freq, 40000000); } int duty = args[ARG_duty].u_int; int duty_u16 = args[ARG_duty_u16].u_int; int duty_ns = args[ARG_duty_ns].u_int; - if (((duty != -1) && (duty_u16 != -1)) || ((duty != -1) && (duty_ns != -1)) || ((duty_u16 != -1) && (duty_ns != -1))) { - mp_raise_ValueError(MP_ERROR_TEXT("only one of parameters 'duty', 'duty_u16' or 'duty_ns' is allowed")); + if (duty_u16 >= 0) { + check_duty_u16(self, duty_u16); + } else if (duty_ns >= 0) { + check_duty_ns(self, duty_ns); + } else if (duty >= 0) { + check_duty_u10(self, duty); + } else if (self->duty_scale == 0) { + self->duty_scale = DUTY_16; + self->duty_ui = PWM_DUTY; + } + + self->output_invert = args[ARG_invert].u_bool; + + // Check the current mode and channel + int mode = -1; + int channel = -1; + find_channel(self, &mode, &channel, freq); + if (channel < 0) { + mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("out of %sPWM channels:%d"), self->lightsleep ? "light sleep capable " : "", self->lightsleep ? LEDC_CHANNEL_MAX : LEDC_SPEED_MODE_MAX *LEDC_CHANNEL_MAX); } + self->mode = mode; + self->channel = channel; - int freq = args[ARG_freq].u_int; // Check if freq wasn't passed as an argument - if (freq == -1) { + if ((freq == -1) && (mode >= 0) && (channel >= 0)) { // Check if already set, otherwise use the default freq. // It is possible in case: // pwm = PWM(pin, freq=1000, duty=256) // pwm = PWM(pin, duty=128) - if (chans[channel_idx].timer_idx != -1) { - freq = timers[chans[channel_idx].timer_idx].freq_hz; + if (chans[mode][channel].timer >= 0) { + freq = timers[mode][chans[mode][channel].timer].freq; } if (freq <= 0) { freq = PWM_FREQ; } } - if ((freq <= 0) || (freq > 40000000)) { - mp_raise_ValueError(MP_ERROR_TEXT("frequency must be from 1Hz to 40MHz")); - } - - int timer_idx; - int current_timer_idx = chans[channel_idx].timer_idx; - bool current_in_use = is_timer_in_use(channel_idx, current_timer_idx); - if (current_in_use) { - timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, CHANNEL_IDX_TO_MODE(channel_idx)); - } else { - timer_idx = chans[channel_idx].timer_idx; - } - - if (timer_idx == -1) { - timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, ANY_MODE); - } - if (timer_idx == -1) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM timers:%d"), PWM_TIMER_MAX); // in all modes - } - - int mode = TIMER_IDX_TO_MODE(timer_idx); - if (CHANNEL_IDX_TO_MODE(channel_idx) != mode) { - // unregister old channel - chans[channel_idx].pin = -1; - chans[channel_idx].timer_idx = -1; - // find new channel - channel_idx = find_channel(self->pin, mode); - if (CHANNEL_IDX_TO_MODE(channel_idx) != mode) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM channels:%d"), PWM_CHANNEL_MAX); // in current mode - } - } - self->mode = mode; - self->timer = TIMER_IDX_TO_TIMER(timer_idx); - self->channel = CHANNEL_IDX_TO_CHANNEL(channel_idx); - - // New PWM assignment - if ((chans[channel_idx].pin == -1) || (chans[channel_idx].timer_idx != timer_idx)) { - configure_channel(self); - chans[channel_idx].pin = self->pin; - } - chans[channel_idx].timer_idx = timer_idx; - self->active = true; - - // Set timer frequency - set_freq(self, freq, &timers[timer_idx]); + set_freq_duty(self, freq); +} - // Set duty cycle? - if (duty_u16 != -1) { - set_duty_u16(self, duty_u16); - } else if (duty_ns != -1) { - set_duty_ns(self, duty_ns); - } else if (duty != -1) { - set_duty_u10(self, duty); - } else if (self->duty_x == 0) { - set_duty_u10(self, (1 << PWM_RES_10_BIT) / 2); // 50% - } +static void self_reset(machine_pwm_obj_t *self) { + self->mode = -1; + self->channel = -1; + self->timer = -1; + self->freq = -1; + self->duty_scale = 0; + self->duty_ui = 0; + self->channel_duty = -1; + self->output_invert = false; + self->output_invert_prev = false; + self->lightsleep = false; } // This called from PWM() constructor static mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 1, 2, true); - gpio_num_t pin_id = machine_pin_get_id(args[0]); - - // create PWM object from the given pin - machine_pwm_obj_t *self = mp_obj_malloc(machine_pwm_obj_t, &machine_pwm_type); - self->pin = pin_id; - self->active = false; - self->mode = -1; - self->channel = -1; - self->timer = -1; - self->duty_x = 0; // start the PWM subsystem if it's not already running if (!pwm_inited) { pwm_init(); + #if FADE + ledc_fade_func_install(0); + #endif pwm_inited = true; } - // start the PWM running for this channel + // create PWM object from the given pin + machine_pwm_obj_t *self = mp_obj_malloc(machine_pwm_obj_t, &machine_pwm_type); + self_reset(self); + + self->pin = machine_pin_get_id(args[0]); + + // Process the remaining parameters. mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); mp_machine_pwm_init_helper(self, n_args - 1, args + 1, &kw_args); - return MP_OBJ_FROM_PTR(self); } // This called from pwm.deinit() method static void mp_machine_pwm_deinit(machine_pwm_obj_t *self) { - int channel_idx = CHANNEL_IDX(self->mode, self->channel); - pwm_deinit(channel_idx); - self->active = false; - self->mode = -1; - self->channel = -1; - self->timer = -1; - self->duty_x = 0; + pwm_deinit(self->mode, self->channel, self->output_invert); + self_reset(self); } // Set and get methods of PWM class @@ -608,57 +756,11 @@ static mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self) { static void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) { pwm_is_active(self); - if ((freq <= 0) || (freq > 40000000)) { - mp_raise_ValueError(MP_ERROR_TEXT("frequency must be from 1Hz to 40MHz")); - } - if (freq == timers[TIMER_IDX(self->mode, self->timer)].freq_hz) { + check_freq_ranges(self, freq, 40000000); + if (freq == timers[self->mode][self->timer].freq) { return; } - - int current_timer_idx = chans[CHANNEL_IDX(self->mode, self->channel)].timer_idx; - bool current_in_use = is_timer_in_use(CHANNEL_IDX(self->mode, self->channel), current_timer_idx); - - // Check if an already running timer with the same freq is running - int new_timer_idx = find_timer(freq, SAME_FREQ_ONLY, self->mode); - - // If no existing timer was found, and the current one is in use, then find a new one - if ((new_timer_idx == -1) && current_in_use) { - // Have to find a new timer - new_timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, self->mode); - - if (new_timer_idx == -1) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM timers:%d"), PWM_TIMER_MAX); // in current mode - } - } - - if ((new_timer_idx != -1) && (new_timer_idx != current_timer_idx)) { - // Bind the channel to the new timer - chans[self->channel].timer_idx = new_timer_idx; - - if (ledc_bind_channel_timer(self->mode, self->channel, TIMER_IDX_TO_TIMER(new_timer_idx)) != ESP_OK) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("failed to bind timer to channel")); - } - - if (!current_in_use) { - // Free the old timer - check_esp_err(ledc_timer_pause(self->mode, self->timer)); - ledc_timer_config_t timer_config = { - .deconfigure = true, - .speed_mode = self->mode, - .timer_num = self->timer, - }; - check_esp_err(ledc_timer_config(&timer_config)); - // Flag it unused - timers[current_timer_idx].freq_hz = -1; - } - - current_timer_idx = new_timer_idx; - } - self->mode = TIMER_IDX_TO_MODE(current_timer_idx); - self->timer = TIMER_IDX_TO_TIMER(current_timer_idx); - - // Set the frequency - set_freq(self, freq, &timers[current_timer_idx]); + set_freq_duty(self, freq); } static mp_obj_t mp_machine_pwm_duty_get(machine_pwm_obj_t *self) { From a72454519362dd1de5eff9e2eb268e6ea1040a40 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Wed, 19 Feb 2025 09:23:04 +0200 Subject: [PATCH 117/154] esp32/mpconfigport: Document how to get more debug info. Signed-off-by: Ihor Nehrutsa --- ports/esp32/mpconfigport.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 7c26009578928..35c8ff1831dbf 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -62,7 +62,8 @@ #define MICROPY_STACK_CHECK_MARGIN (1024) #define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) -#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_NORMAL) +#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_NORMAL) // Debugging Note: Increase the error reporting level to view + // __FUNCTION__, __LINE__, __FILE__ in check_esp_err() exceptions #define MICROPY_WARNINGS (1) #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) #define MICROPY_STREAMS_POSIX_API (1) From 9d565182d757e7a9bdf95bcb2b18fe2d6fa4111a Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Thu, 13 Mar 2025 13:46:46 +0200 Subject: [PATCH 118/154] docs/esp32/quickref: Add PWM lightsleep example. Signed-off-by: Ihor Nehrutsa --- docs/esp32/quickref.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 707ae27c08cf2..56d8b0e4f6bc9 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -383,7 +383,7 @@ for more details. Use the :ref:`machine.PWM ` class:: - from machine import Pin, PWM + from machine import Pin, PWM, lightsleep pwm0 = PWM(Pin(0), freq=5000, duty_u16=32768) # create PWM object from a pin freq = pwm0.freq() # get current frequency @@ -409,6 +409,9 @@ Use the :ref:`machine.PWM ` class:: pwm4 = PWM(Pin(4), lightsleep=True) # Allow PWM during light sleep mode + lightsleep(10*1000) # pwm0, pwm2 goes off, pwm4 stays on during 10s light sleep + # pwm0, pwm2, pwm4 on after 10s light sleep + ESP chips have different hardware peripherals: ======================================================= ======== ========= ========== From e1ab04e8205f78bb3ed9d0368bc1bb013b1bdcf9 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 12 May 2025 13:18:33 +1000 Subject: [PATCH 119/154] esp32/mpthreadport: Fix double delete of tasks on soft reset. Python threads (created via the `_thread` module) are backed by a FreeRTOS task. Managing the deletion of the task can be tricky, and there are currently some bugs with this in the esp32 port. The actual crash seen was in FreeRTOS' `uxListRemove()`, and that's because of two calls to `vTaskDelete()` for the same task: one in `freertos_entry()` when the task ran to completion, and the other in `mp_thread_deinit()`. The latter tried to delete the task a second time because it was still in the linked list, because `vTaskPreDeletionHook()` had not yet been called. And the reason `vTaskPreDeletionHook()` was yet to be called is because the FreeRTOS idle task was starved. This commit fixes that. There are three things done by this commit: - remove the `vTaskPreDeletionHook`, it's not needed anymore because task stack memory is allocated by the IDF, not on the MicroPython heap - when a task finishes it now removes itself from the linked list, just before it deletes itself - on soft reset, all tasks are deleted and removed from the linked list in one swoop (while the mutex is held) Signed-off-by: Damien George --- ports/esp32/boards/sdkconfig.base | 1 - ports/esp32/mpthreadport.c | 96 ++++++++++++------------------- 2 files changed, 37 insertions(+), 60 deletions(-) diff --git a/ports/esp32/boards/sdkconfig.base b/ports/esp32/boards/sdkconfig.base index 530db427119ca..5595444e86f39 100644 --- a/ports/esp32/boards/sdkconfig.base +++ b/ports/esp32/boards/sdkconfig.base @@ -50,7 +50,6 @@ CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2 CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP=n -CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK=y # UDP CONFIG_LWIP_PPP_SUPPORT=y diff --git a/ports/esp32/mpthreadport.c b/ports/esp32/mpthreadport.c index 962b5780d08dd..67917e33e5189 100644 --- a/ports/esp32/mpthreadport.c +++ b/ports/esp32/mpthreadport.c @@ -41,10 +41,16 @@ #define MP_THREAD_DEFAULT_STACK_SIZE (MP_THREAD_MIN_STACK_SIZE + MICROPY_STACK_CHECK_MARGIN) #define MP_THREAD_PRIORITY (ESP_TASK_PRIO_MIN + 1) +typedef enum { + MP_THREAD_RUN_STATE_NEW, + MP_THREAD_RUN_STATE_RUNNING, + MP_THREAD_RUN_STATE_FINISHED, +} mp_thread_run_state_t; + // this structure forms a linked list, one node per active thread typedef struct _mp_thread_t { TaskHandle_t id; // system id of thread - int ready; // whether the thread is ready and running + mp_thread_run_state_t run_state; // current run state of the thread void *arg; // thread Python args, a GC root pointer void *stack; // pointer to the stack size_t stack_len; // number of words in the stack @@ -60,18 +66,16 @@ void mp_thread_init(void *stack, uint32_t stack_len) { mp_thread_set_state(&mp_state_ctx.thread); // create the first entry in the linked list of all threads thread_entry0.id = xTaskGetCurrentTaskHandle(); - thread_entry0.ready = 1; + thread_entry0.run_state = MP_THREAD_RUN_STATE_RUNNING; thread_entry0.arg = NULL; thread_entry0.stack = stack; thread_entry0.stack_len = stack_len; thread_entry0.next = NULL; + thread = &thread_entry0; mp_thread_mutex_init(&thread_mutex); // memory barrier to ensure above data is committed __sync_synchronize(); - - // vTaskPreDeletionHook needs the thread ready after thread_mutex is ready - thread = &thread_entry0; } void mp_thread_gc_others(void) { @@ -82,7 +86,7 @@ void mp_thread_gc_others(void) { if (th->id == xTaskGetCurrentTaskHandle()) { continue; } - if (!th->ready) { + if (th->run_state != MP_THREAD_RUN_STATE_RUNNING) { continue; } gc_collect_root(th->stack, th->stack_len); @@ -106,7 +110,7 @@ void mp_thread_start(void) { mp_thread_mutex_lock(&thread_mutex, 1); for (mp_thread_t *th = thread; th != NULL; th = th->next) { if (th->id == xTaskGetCurrentTaskHandle()) { - th->ready = 1; + th->run_state = MP_THREAD_RUN_STATE_RUNNING; break; } } @@ -116,12 +120,22 @@ void mp_thread_start(void) { static void *(*ext_thread_entry)(void *) = NULL; static void freertos_entry(void *arg) { + // Run the Python code. if (ext_thread_entry) { ext_thread_entry(arg); } - vTaskDelete(NULL); - for (;;) {; + + // Remove the thread from the linked-list of active threads. + mp_thread_mutex_lock(&thread_mutex, 1); + for (mp_thread_t **th = &thread; *th != NULL; th = &(*th)->next) { + if ((*th)->id == xTaskGetCurrentTaskHandle()) { + *th = (*th)->next; + } } + mp_thread_mutex_unlock(&thread_mutex); + + // Delete this FreeRTOS task (this call to vTaskDelete will not return). + vTaskDelete(NULL); } mp_uint_t mp_thread_create_ex(void *(*entry)(void *), void *arg, size_t *stack_size, int priority, char *name) { @@ -147,7 +161,7 @@ mp_uint_t mp_thread_create_ex(void *(*entry)(void *), void *arg, size_t *stack_s } // add thread to linked list of all threads - th->ready = 0; + th->run_state = MP_THREAD_RUN_STATE_NEW; th->arg = arg; th->stack = pxTaskGetStackStart(th->id); th->stack_len = *stack_size / sizeof(uintptr_t); @@ -167,33 +181,7 @@ void mp_thread_finish(void) { mp_thread_mutex_lock(&thread_mutex, 1); for (mp_thread_t *th = thread; th != NULL; th = th->next) { if (th->id == xTaskGetCurrentTaskHandle()) { - th->ready = 0; - break; - } - } - mp_thread_mutex_unlock(&thread_mutex); -} - -// This is called either from vTaskDelete() or from the FreeRTOS idle task, so -// may not be within Python context. Therefore MP_STATE_THREAD may not be valid -// and it does not have the GIL. -void vTaskPreDeletionHook(void *tcb) { - if (thread == NULL) { - // threading not yet initialised - return; - } - mp_thread_t *prev = NULL; - mp_thread_mutex_lock(&thread_mutex, 1); - for (mp_thread_t *th = thread; th != NULL; prev = th, th = th->next) { - // unlink the node from the list - if ((void *)th->id == tcb) { - if (prev != NULL) { - prev->next = th->next; - } else { - // move the start pointer - thread = th->next; - } - // The "th" memory will eventually be reclaimed by the GC. + th->run_state = MP_THREAD_RUN_STATE_FINISHED; break; } } @@ -221,32 +209,22 @@ void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex) { } void mp_thread_deinit(void) { - for (;;) { - // Find a task to delete - TaskHandle_t id = NULL; - mp_thread_mutex_lock(&thread_mutex, 1); - for (mp_thread_t *th = thread; th != NULL; th = th->next) { - // Don't delete the current task - if (th->id != xTaskGetCurrentTaskHandle()) { - id = th->id; - break; - } - } - mp_thread_mutex_unlock(&thread_mutex); + // The current task should be thread_entry0 and should be the last in the linked list. + assert(thread_entry0.id == xTaskGetCurrentTaskHandle()); + assert(thread_entry0.next == NULL); - if (id == NULL) { - // No tasks left to delete - break; - } else { - // Call FreeRTOS to delete the task (it will call vTaskPreDeletionHook) - vTaskDelete(id); + // Delete all tasks except the main one. + mp_thread_mutex_lock(&thread_mutex, 1); + for (mp_thread_t *th = thread; th != NULL; th = th->next) { + if (th != &thread_entry0) { + vTaskDelete(th->id); } } -} - -#else + thread = &thread_entry0; + mp_thread_mutex_unlock(&thread_mutex); -void vTaskPreDeletionHook(void *tcb) { + // Give the idle task a chance to run, to clean up any deleted tasks. + vTaskDelay(1); } #endif // MICROPY_PY_THREAD From f48b9815671eb81e99339dc443fb20dca1c72e69 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Sun, 16 Feb 2025 12:58:50 +0200 Subject: [PATCH 120/154] esp32: Update to use ESP-IDF v5.4.1. This version of the IDF uses about 1KB more IRAM and 1KB more DRAM on most boards, but 6.5KB more DRAM usage on the S3. It seems that's due to a lot of small increases in many components. Signed-off-by: Ihor Nehrutsa --- ports/esp32/README.md | 8 ++++---- ports/esp32/tools/metrics_esp32.py | 2 +- tools/ci.sh | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ports/esp32/README.md b/ports/esp32/README.md index d9115072a45c3..e11c64ad70797 100644 --- a/ports/esp32/README.md +++ b/ports/esp32/README.md @@ -31,7 +31,7 @@ manage the ESP32 microcontroller, as well as a way to manage the required build environment and toolchains needed to build the firmware. The ESP-IDF changes quickly and MicroPython only supports certain versions. -Currently MicroPython supports v5.2, v5.2.2, v5.3 and v5.4. +Currently MicroPython supports v5.2, v5.2.2, v5.3, v5.4 and v5.4.1. To install the ESP-IDF the full instructions can be found at the [Espressif Getting Started guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html#installation-step-by-step). @@ -49,10 +49,10 @@ The steps to take are summarised below. To check out a copy of the IDF use git clone: ```bash -$ git clone -b v5.2.2 --recursive https://github.com/espressif/esp-idf.git +$ git clone -b v5.4.1 --recursive https://github.com/espressif/esp-idf.git ``` -You can replace `v5.2.2` with any other supported version. +You can replace `v5.4.1` with any other supported version. (You don't need a full recursive clone; see the `ci_esp32_setup` function in `tools/ci.sh` in this repository for more detailed set-up commands.) @@ -61,7 +61,7 @@ MicroPython and update the submodules using: ```bash $ cd esp-idf -$ git checkout v5.2.2 +$ git checkout v5.4.1 $ git submodule update --init --recursive ``` diff --git a/ports/esp32/tools/metrics_esp32.py b/ports/esp32/tools/metrics_esp32.py index 5e65a78c97a27..9efaae63a9baf 100755 --- a/ports/esp32/tools/metrics_esp32.py +++ b/ports/esp32/tools/metrics_esp32.py @@ -37,7 +37,7 @@ import subprocess from dataclasses import dataclass -IDF_VERS = ("v5.2.2",) +IDF_VERS = ("v5.4.1",) BUILDS = ( ("ESP32_GENERIC", ""), diff --git a/tools/ci.sh b/tools/ci.sh index a4bd43567c861..d12b4bcd31645 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -159,7 +159,7 @@ function ci_cc3200_build { # ports/esp32 # GitHub tag of ESP-IDF to use for CI (note: must be a tag or a branch) -IDF_VER=v5.2.2 +IDF_VER=v5.4.1 PYTHON=$(command -v python3 2> /dev/null) PYTHON_VER=$(${PYTHON:-python} --version | cut -d' ' -f2) From 10f6c0699e52857a4f7350a5d3a9561862c98a8d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 5 Feb 2025 10:42:55 +1100 Subject: [PATCH 121/154] esp32/network_lan: Add support for LAN8670 PHY. This adds support for LAN8670 to the esp32 port. Enabled conditionally for the esp32 target, if ESP-IDF version is new enough (v5.3 or newer). Fixes issue #15731. Signed-off-by: Damien George Signed-off-by: Angus Gratton --- ports/esp32/main/idf_component.yml | 5 +++++ ports/esp32/modnetwork.h | 17 ++++++++++++++++- ports/esp32/modnetwork_globals.h | 3 +++ ports/esp32/network_lan.c | 12 ++++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/ports/esp32/main/idf_component.yml b/ports/esp32/main/idf_component.yml index ccbde1f27e412..f7773f4f4e472 100644 --- a/ports/esp32/main/idf_component.yml +++ b/ports/esp32/main/idf_component.yml @@ -5,5 +5,10 @@ dependencies: rules: - if: "target in [esp32s2, esp32s3]" version: "~1.0.0" + espressif/lan867x: + version: "~1.0.0" + rules: + - if: "target == esp32" + - if: "idf_version >=5.3" idf: version: ">=5.2.0" diff --git a/ports/esp32/modnetwork.h b/ports/esp32/modnetwork.h index 383e200b983c2..4debea1114b74 100644 --- a/ports/esp32/modnetwork.h +++ b/ports/esp32/modnetwork.h @@ -28,7 +28,22 @@ #include "esp_netif.h" -enum { PHY_LAN8710, PHY_LAN8720, PHY_IP101, PHY_RTL8201, PHY_DP83848, PHY_KSZ8041, PHY_KSZ8081, PHY_KSZ8851SNL = 100, PHY_DM9051, PHY_W5500 }; +// lan867x component requires newer IDF version +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) && CONFIG_IDF_TARGET_ESP32 +#define PHY_LAN867X_ENABLED (1) +#else +#define PHY_LAN867X_ENABLED (0) +#endif + +enum { + // PHYs supported by the internal Ethernet MAC: + PHY_LAN8710, PHY_LAN8720, PHY_IP101, PHY_RTL8201, PHY_DP83848, PHY_KSZ8041, PHY_KSZ8081, + #if PHY_LAN867X_ENABLED + PHY_LAN8670, + #endif + // PHYs which are actually SPI Ethernet MAC+PHY chips: + PHY_KSZ8851SNL = 100, PHY_DM9051, PHY_W5500 +}; #define IS_SPI_PHY(NUM) (NUM >= 100) enum { ETH_INITIALIZED, ETH_STARTED, ETH_STOPPED, ETH_CONNECTED, ETH_DISCONNECTED, ETH_GOT_IP }; diff --git a/ports/esp32/modnetwork_globals.h b/ports/esp32/modnetwork_globals.h index 9e355e958f1a4..9909e7c056a4f 100644 --- a/ports/esp32/modnetwork_globals.h +++ b/ports/esp32/modnetwork_globals.h @@ -47,6 +47,9 @@ { MP_ROM_QSTR(MP_QSTR_PHY_DP83848), MP_ROM_INT(PHY_DP83848) }, { MP_ROM_QSTR(MP_QSTR_PHY_KSZ8041), MP_ROM_INT(PHY_KSZ8041) }, { MP_ROM_QSTR(MP_QSTR_PHY_KSZ8081), MP_ROM_INT(PHY_KSZ8081) }, +#if PHY_LAN867X_ENABLED +{ MP_ROM_QSTR(MP_QSTR_PHY_LAN8670), MP_ROM_INT(PHY_LAN8670) }, +#endif #if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL { MP_ROM_QSTR(MP_QSTR_PHY_KSZ8851SNL), MP_ROM_INT(PHY_KSZ8851SNL) }, diff --git a/ports/esp32/network_lan.c b/ports/esp32/network_lan.c index e1a8c957857c4..bf6b565e8cd19 100644 --- a/ports/esp32/network_lan.c +++ b/ports/esp32/network_lan.c @@ -45,6 +45,10 @@ #include "modnetwork.h" #include "extmod/modnetwork.h" +#if PHY_LAN867X_ENABLED +#include "esp_eth_phy_lan867x.h" +#endif + typedef struct _lan_if_obj_t { base_if_obj_t base; bool initialized; @@ -156,6 +160,9 @@ static mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar args[ARG_phy_type].u_int != PHY_RTL8201 && args[ARG_phy_type].u_int != PHY_KSZ8041 && args[ARG_phy_type].u_int != PHY_KSZ8081 && + #if PHY_LAN867X_ENABLED + args[ARG_phy_type].u_int != PHY_LAN8670 && + #endif #if CONFIG_ETH_USE_SPI_ETHERNET #if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL args[ARG_phy_type].u_int != PHY_KSZ8851SNL && @@ -231,7 +238,12 @@ static mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar case PHY_KSZ8081: self->phy = esp_eth_phy_new_ksz80xx(&phy_config); break; + #if PHY_LAN867X_ENABLED + case PHY_LAN8670: + self->phy = esp_eth_phy_new_lan867x(&phy_config); + break; #endif + #endif // CONFIG_IDF_TARGET_ESP32 #if CONFIG_ETH_USE_SPI_ETHERNET #if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL case PHY_KSZ8851SNL: { From 06d8c084b90cb6e56ab9be0de040dcae439ce261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Tue, 1 Mar 2022 22:29:58 +0100 Subject: [PATCH 122/154] esp32/modesp32: Implement esp32.idf_task_info(). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a new function, `esp32.idf_task_info()`, that can be used to retrieve task statistics which is useful for diagnosing issues where some tasks are using up a lot of CPU time. It's best used in conjunction with the `utop` module from micropython-lib. Signed-off-by: DaniĆ«l van de Giessen --- docs/library/esp32.rst | 23 +++++++++++++++++++++ ports/esp32/modesp32.c | 46 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index dc35e7905e162..24831c58d6d20 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -80,6 +80,29 @@ Functions The result of :func:`gc.mem_free()` is the total of the current "free" and "max new split" values printed by :func:`micropython.mem_info()`. +.. function:: idf_task_info() + + Returns information about running ESP-IDF/FreeRTOS tasks, which include + MicroPython threads. This data is useful to gain insight into how much time + tasks spend running or if they are blocked for significant parts of time, + and to determine if allocated stacks are fully utilized or might be reduced. + + ``CONFIG_FREERTOS_USE_TRACE_FACILITY=y`` must be set in the board + configuration to make this method available. Additionally configuring + ``CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y`` and + ``CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y`` is recommended to be able to + retrieve the total and per-task runtime and the core ID respectively. + + The return value is a 2-tuple where the first value is the total runtime, + and the second a list of tasks. Each task is a 7-tuple containing: the task + ID, name, current state, priority, runtime, stack high water mark, and the + ID of the core it is running on. Runtime and core ID will be None when the + respective FreeRTOS configuration option is not enabled. + + .. note:: For an easier to use output based on this function you can use the + `utop library `_, + which implements a live overview similar to the Unix ``top`` command. + Flash partitions ---------------- diff --git a/ports/esp32/modesp32.c b/ports/esp32/modesp32.c index 164b1918261f4..0296ddf10e775 100644 --- a/ports/esp32/modesp32.c +++ b/ports/esp32/modesp32.c @@ -212,6 +212,49 @@ static mp_obj_t esp32_idf_heap_info(const mp_obj_t cap_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(esp32_idf_heap_info_obj, esp32_idf_heap_info); +#if CONFIG_FREERTOS_USE_TRACE_FACILITY +static mp_obj_t esp32_idf_task_info(void) { + const size_t task_count_max = uxTaskGetNumberOfTasks(); + TaskStatus_t *task_array = m_new(TaskStatus_t, task_count_max); + uint32_t total_time; + const size_t task_count = uxTaskGetSystemState(task_array, task_count_max, &total_time); + + mp_obj_list_t *task_list = MP_OBJ_TO_PTR(mp_obj_new_list(task_count, NULL)); + for (size_t i = 0; i < task_count; i++) { + mp_obj_t task_data[] = { + mp_obj_new_int_from_uint((mp_uint_t)task_array[i].xHandle), + mp_obj_new_str(task_array[i].pcTaskName, strlen(task_array[i].pcTaskName)), + MP_OBJ_NEW_SMALL_INT(task_array[i].eCurrentState), + MP_OBJ_NEW_SMALL_INT(task_array[i].uxCurrentPriority), + #if CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS + mp_obj_new_int_from_uint(task_array[i].ulRunTimeCounter), + #else + mp_const_none, + #endif + mp_obj_new_int_from_uint(task_array[i].usStackHighWaterMark), + #if CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID + MP_OBJ_NEW_SMALL_INT(task_array[i].xCoreID), + #else + mp_const_none, + #endif + }; + task_list->items[i] = mp_obj_new_tuple(7, task_data); + } + + m_del(TaskStatus_t, task_array, task_count_max); + mp_obj_t task_stats[] = { + #if CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS + MP_OBJ_NEW_SMALL_INT(total_time), + #else + mp_const_none, + #endif + task_list + }; + return mp_obj_new_tuple(2, task_stats); +} +static MP_DEFINE_CONST_FUN_OBJ_0(esp32_idf_task_info_obj, esp32_idf_task_info); +#endif + static const mp_rom_map_elem_t esp32_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_esp32) }, @@ -228,6 +271,9 @@ static const mp_rom_map_elem_t esp32_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_mcu_temperature), MP_ROM_PTR(&esp32_mcu_temperature_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_idf_heap_info), MP_ROM_PTR(&esp32_idf_heap_info_obj) }, + #if CONFIG_FREERTOS_USE_TRACE_FACILITY + { MP_ROM_QSTR(MP_QSTR_idf_task_info), MP_ROM_PTR(&esp32_idf_task_info_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_NVS), MP_ROM_PTR(&esp32_nvs_type) }, { MP_ROM_QSTR(MP_QSTR_Partition), MP_ROM_PTR(&esp32_partition_type) }, From 90aeac800a0ac861c64dda4b99db1a3f0ed91cea Mon Sep 17 00:00:00 2001 From: Rick Sorensen Date: Wed, 16 Apr 2025 09:28:39 -0500 Subject: [PATCH 123/154] esp32/machine_i2c: Fix default I2C pins for C3, S3. The default I2C init does not require setting SCL or SDA but the default I2C0 pins for C3, S3 conflict with the espressif GPIO usage. For the C3, pins 18/19 are for USB/JTAG. If used for I2C() they will cause the REPL to hang on initialization of the I2C. For the S3 pin 19 is allocated for USB/JTAG also but the defaults do not seem to affect the REPL. See related #16956. Fixes issue #17103. Signed-off-by: Rick Sorensen Signed-off-by: Angus Gratton --- ports/esp32/machine_i2c.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/esp32/machine_i2c.c b/ports/esp32/machine_i2c.c index 12b86e4aa0294..259101ee7e145 100644 --- a/ports/esp32/machine_i2c.c +++ b/ports/esp32/machine_i2c.c @@ -35,9 +35,14 @@ #if MICROPY_PY_MACHINE_I2C || MICROPY_PY_MACHINE_SOFTI2C #ifndef MICROPY_HW_I2C0_SCL +#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 +#define MICROPY_HW_I2C0_SCL (GPIO_NUM_9) +#define MICROPY_HW_I2C0_SDA (GPIO_NUM_8) +#else #define MICROPY_HW_I2C0_SCL (GPIO_NUM_18) #define MICROPY_HW_I2C0_SDA (GPIO_NUM_19) #endif +#endif #ifndef MICROPY_HW_I2C1_SCL #if CONFIG_IDF_TARGET_ESP32 From ecbbc512b2083c472f7bec45e8fae521e7af22aa Mon Sep 17 00:00:00 2001 From: Elvis Pfutzenreuter Date: Thu, 1 May 2025 14:30:04 -0300 Subject: [PATCH 124/154] esp32/network_lan: Add PHY_GENERIC device type. Support the new PHY_GENERIC device type, added in ESP-IDF v5.4.0 [1]. This PHY driver was added to ESP-IDF to support "generic"/oddball PHY LAN chips like the JL1101, which offer no features beyond the bare 802.3 PHY standard and don't actually need a chip-specific driver (see discussion at [2]). [1] https://github.com/espressif/esp-idf/commit/0738314308ad36a73601ddb8bb82f1dcbfe1f550 [2] https://github.com/espressif/esp-eth-drivers/pull/28 Signed-off-by: Elvis Pfutzenreuter --- docs/esp32/quickref.rst | 1 + ports/esp32/modnetwork.h | 10 ++++++++++ ports/esp32/modnetwork_globals.h | 3 +++ ports/esp32/network_lan.c | 8 ++++++++ 4 files changed, 22 insertions(+) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 56d8b0e4f6bc9..4e70ff255ec48 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -148,6 +148,7 @@ Required keyword arguments for the constructor: - ``mdc`` and ``mdio`` - :class:`machine.Pin` objects (or integers) specifying the MDC and MDIO pins. - ``phy_type`` - Select the PHY device type. Supported devices are + ``PHY_GENERIC``, ``PHY_LAN8710``, ``PHY_LAN8720``, ``PHY_IP101``, ``PHY_RTL8201``, ``PHY_DP83848``, ``PHY_KSZ8041`` and ``PHY_KSZ8081``. These values are all constants defined in the ``network`` module. diff --git a/ports/esp32/modnetwork.h b/ports/esp32/modnetwork.h index 4debea1114b74..ba69d5cc0f901 100644 --- a/ports/esp32/modnetwork.h +++ b/ports/esp32/modnetwork.h @@ -35,12 +35,22 @@ #define PHY_LAN867X_ENABLED (0) #endif +// PHY_GENERIC support requires newer IDF version +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) && CONFIG_IDF_TARGET_ESP32 +#define PHY_GENERIC_ENABLED (1) +#else +#define PHY_GENERIC_ENABLED (0) +#endif + enum { // PHYs supported by the internal Ethernet MAC: PHY_LAN8710, PHY_LAN8720, PHY_IP101, PHY_RTL8201, PHY_DP83848, PHY_KSZ8041, PHY_KSZ8081, #if PHY_LAN867X_ENABLED PHY_LAN8670, #endif + #if PHY_GENERIC_ENABLED + PHY_GENERIC, + #endif // PHYs which are actually SPI Ethernet MAC+PHY chips: PHY_KSZ8851SNL = 100, PHY_DM9051, PHY_W5500 }; diff --git a/ports/esp32/modnetwork_globals.h b/ports/esp32/modnetwork_globals.h index 9909e7c056a4f..12252ddbc5de6 100644 --- a/ports/esp32/modnetwork_globals.h +++ b/ports/esp32/modnetwork_globals.h @@ -50,6 +50,9 @@ #if PHY_LAN867X_ENABLED { MP_ROM_QSTR(MP_QSTR_PHY_LAN8670), MP_ROM_INT(PHY_LAN8670) }, #endif +#if PHY_GENERIC_ENABLED +{ MP_ROM_QSTR(MP_QSTR_PHY_GENERIC), MP_ROM_INT(PHY_GENERIC) }, +#endif #if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL { MP_ROM_QSTR(MP_QSTR_PHY_KSZ8851SNL), MP_ROM_INT(PHY_KSZ8851SNL) }, diff --git a/ports/esp32/network_lan.c b/ports/esp32/network_lan.c index bf6b565e8cd19..309ee0b14a23c 100644 --- a/ports/esp32/network_lan.c +++ b/ports/esp32/network_lan.c @@ -163,6 +163,9 @@ static mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar #if PHY_LAN867X_ENABLED args[ARG_phy_type].u_int != PHY_LAN8670 && #endif + #if PHY_GENERIC_ENABLED + args[ARG_phy_type].u_int != PHY_GENERIC && + #endif #if CONFIG_ETH_USE_SPI_ETHERNET #if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL args[ARG_phy_type].u_int != PHY_KSZ8851SNL && @@ -243,6 +246,11 @@ static mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar self->phy = esp_eth_phy_new_lan867x(&phy_config); break; #endif + #if PHY_GENERIC_ENABLED + case PHY_GENERIC: + self->phy = esp_eth_phy_new_generic(&phy_config); + break; + #endif #endif // CONFIG_IDF_TARGET_ESP32 #if CONFIG_ETH_USE_SPI_ETHERNET #if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL From 1dfb5092fc66b70420af5a6b8d6239e70b8c6e13 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Sat, 26 Apr 2025 23:41:32 +0200 Subject: [PATCH 125/154] tools/mpremote: Add new 'fs tree' command. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `mpremote fs tree` command to show a tree of the device's files. It: - Shows a treeview from current path or specified path. - Uses the graph chars ("ā”œā”€ā”€ ", "└── ") (not configurable). - Has the options: -v/--verbose adds the serial device name to the top of the tree -s/--size add a size to the files -h/--human add a human readable size to the files Signed-off-by: Jos Verlinde --- tools/mpremote/mpremote/commands.py | 49 +++++++++++++++++++++++++++-- tools/mpremote/mpremote/main.py | 34 +++++++++++++++++--- 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/tools/mpremote/mpremote/commands.py b/tools/mpremote/mpremote/commands.py index 452384728ab71..428600baf4375 100644 --- a/tools/mpremote/mpremote/commands.py +++ b/tools/mpremote/mpremote/commands.py @@ -334,6 +334,49 @@ def do_filesystem_recursive_rm(state, path, args): print(f"removed: '{path}'") +def human_size(size, decimals=1): + for unit in ['B', 'K', 'M', 'G', 'T']: + if size < 1024.0 or unit == 'T': + break + size /= 1024.0 + return f"{size:.{decimals}f}{unit}" if unit != 'B' else f"{int(size)}" + + +def do_filesystem_tree(state, path, args): + """Print a tree of the device's filesystem starting at path.""" + connectors = ("ā”œā”€ā”€ ", "└── ") + + def _tree_recursive(path, prefix=""): + entries = state.transport.fs_listdir(path) + entries.sort(key=lambda e: e.name) + for i, entry in enumerate(entries): + connector = connectors[1] if i == len(entries) - 1 else connectors[0] + is_dir = entry.st_mode & 0x4000 # Directory + size_str = "" + # most MicroPython filesystems don't support st_size on directories, reduce clutter + if entry.st_size > 0 or not is_dir: + if args.size: + size_str = f"[{entry.st_size:>9}] " + elif args.human: + size_str = f"[{human_size(entry.st_size):>6}] " + print(f"{prefix}{connector}{size_str}{entry.name}") + if is_dir: + _tree_recursive( + _remote_path_join(path, entry.name), + prefix + (" " if i == len(entries) - 1 else "│ "), + ) + + if not path or path == ".": + path = state.transport.exec("import os;print(os.getcwd())").strip().decode("utf-8") + if not (path == "." or state.transport.fs_isdir(path)): + raise CommandError(f"tree: '{path}' is not a directory") + if args.verbose: + print(f":{path} on {state.transport.device_name}") + else: + print(f":{path}") + _tree_recursive(path) + + def do_filesystem(state, args): state.ensure_raw_repl() state.did_action() @@ -361,8 +404,8 @@ def do_filesystem(state, args): # leading ':' if the user included them. paths = [path[1:] if path.startswith(":") else path for path in paths] - # ls implicitly lists the cwd. - if command == "ls" and not paths: + # ls and tree implicitly lists the cwd. + if command in ("ls", "tree") and not paths: paths = [""] try: @@ -404,6 +447,8 @@ def do_filesystem(state, args): ) else: do_filesystem_cp(state, path, cp_dest, len(paths) > 1, not args.force) + elif command == "tree": + do_filesystem_tree(state, path, args) except OSError as er: raise CommandError("{}: {}: {}.".format(command, er.strerror, os.strerror(er.errno))) except TransportError as er: diff --git a/tools/mpremote/mpremote/main.py b/tools/mpremote/mpremote/main.py index b30a1a21354cc..f8c913d26d194 100644 --- a/tools/mpremote/mpremote/main.py +++ b/tools/mpremote/mpremote/main.py @@ -181,7 +181,11 @@ def argparse_rtc(): def argparse_filesystem(): - cmd_parser = argparse.ArgumentParser(description="execute filesystem commands on the device") + cmd_parser = argparse.ArgumentParser( + description="execute filesystem commands on the device", + add_help=False, + ) + cmd_parser.add_argument("--help", action="help", help="show this help message and exit") _bool_flag(cmd_parser, "recursive", "r", False, "recursive (for cp and rm commands)") _bool_flag( cmd_parser, @@ -197,10 +201,26 @@ def argparse_filesystem(): None, "enable verbose output (defaults to True for all commands except cat)", ) + size_group = cmd_parser.add_mutually_exclusive_group() + size_group.add_argument( + "--size", + "-s", + default=False, + action="store_true", + help="show file size in bytes(tree command only)", + ) + size_group.add_argument( + "--human", + "-h", + default=False, + action="store_true", + help="show file size in a more human readable way (tree command only)", + ) + cmd_parser.add_argument( "command", nargs=1, - help="filesystem command (e.g. cat, cp, sha256sum, ls, rm, rmdir, touch)", + help="filesystem command (e.g. cat, cp, sha256sum, ls, rm, rmdir, touch, tree)", ) cmd_parser.add_argument("path", nargs="+", help="local and remote paths") return cmd_parser @@ -355,6 +375,7 @@ def argparse_none(description): "rmdir": "fs rmdir", "sha256sum": "fs sha256sum", "touch": "fs touch", + "tree": "fs tree", # Disk used/free. "df": [ "exec", @@ -552,8 +573,13 @@ def main(): command_args = remaining_args extra_args = [] - # Special case: "fs ls" allowed have no path specified. - if cmd == "fs" and len(command_args) == 1 and command_args[0] == "ls": + # Special case: "fs ls" and "fs tree" can have only options and no path specified. + if ( + cmd == "fs" + and len(command_args) >= 1 + and command_args[0] in ("ls", "tree") + and sum(1 for a in command_args if not a.startswith('-')) == 1 + ): command_args.append("") # Use the command-specific argument parser. From d9453164320e8c30b665c5252bbf8a59479ae848 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Sat, 26 Apr 2025 23:41:32 +0200 Subject: [PATCH 126/154] tools/mpremote/tests: Add tests for 'fs tree' command. Signed-off-by: Jos Verlinde --- tools/mpremote/tests/test_filesystem.sh | 3 + tools/mpremote/tests/test_filesystem.sh.exp | 2 + tools/mpremote/tests/test_fs_tree.sh | 114 ++++++++++ tools/mpremote/tests/test_fs_tree.sh.exp | 225 ++++++++++++++++++++ 4 files changed, 344 insertions(+) create mode 100755 tools/mpremote/tests/test_fs_tree.sh create mode 100644 tools/mpremote/tests/test_fs_tree.sh.exp diff --git a/tools/mpremote/tests/test_filesystem.sh b/tools/mpremote/tests/test_filesystem.sh index a29015e987268..13c5394f71c88 100755 --- a/tools/mpremote/tests/test_filesystem.sh +++ b/tools/mpremote/tests/test_filesystem.sh @@ -237,3 +237,6 @@ echo ----- # try to delete existing folder in mounted filesystem $MPREMOTE mount "${TMP}" + rm -rv :package || echo "expect error" echo ----- +# fs without command should raise error +$MPREMOTE fs 2>/dev/null || echo "expect error: $?" +echo ----- diff --git a/tools/mpremote/tests/test_filesystem.sh.exp b/tools/mpremote/tests/test_filesystem.sh.exp index 3d9d0fe9ae8fa..63411580b756e 100644 --- a/tools/mpremote/tests/test_filesystem.sh.exp +++ b/tools/mpremote/tests/test_filesystem.sh.exp @@ -272,3 +272,5 @@ rm :package mpremote: rm -r not permitted on /remote directory expect error ----- +expect error: 2 +----- diff --git a/tools/mpremote/tests/test_fs_tree.sh b/tools/mpremote/tests/test_fs_tree.sh new file mode 100755 index 0000000000000..d7fc433ae6a15 --- /dev/null +++ b/tools/mpremote/tests/test_fs_tree.sh @@ -0,0 +1,114 @@ +#!/bin/bash +set -e + +# Creates a RAM disk big enough to hold two copies of the test directory +# structure. +cat << EOF > "${TMP}/ramdisk.py" +class RAMBlockDev: + def __init__(self, block_size, num_blocks): + self.block_size = block_size + self.data = bytearray(block_size * num_blocks) + + def readblocks(self, block_num, buf): + for i in range(len(buf)): + buf[i] = self.data[block_num * self.block_size + i] + + def writeblocks(self, block_num, buf): + for i in range(len(buf)): + self.data[block_num * self.block_size + i] = buf[i] + + def ioctl(self, op, arg): + if op == 4: # get number of blocks + return len(self.data) // self.block_size + if op == 5: # get block size + return self.block_size + +import os + +bdev = RAMBlockDev(512, 50) +os.VfsFat.mkfs(bdev) +os.mount(bdev, '/ramdisk') +os.chdir('/ramdisk') +EOF + +# setup +echo ----- +$MPREMOTE run "${TMP}/ramdisk.py" +$MPREMOTE resume ls + +echo ----- +echo "empty tree" +$MPREMOTE resume tree : + +echo ----- +$MPREMOTE resume touch :a.py + touch :b.py +$MPREMOTE resume mkdir :foo + touch :foo/aa.py + touch :foo/ba.py + +echo "small tree - :" +$MPREMOTE resume tree : + +echo ----- +echo "no path" +$MPREMOTE resume tree + +echo ----- +echo "path = '.'" +$MPREMOTE resume tree . + +echo ----- +echo "path = ':.'" +$MPREMOTE resume tree :. + + +echo ----- +echo "multiple trees" +$MPREMOTE resume mkdir :bar + touch :bar/aaa.py + touch :bar/bbbb.py +$MPREMOTE resume mkdir :bar/baz + touch :bar/baz/aaa.py + touch :bar/baz/bbbb.py +$MPREMOTE resume mkdir :bar/baz/quux + touch :bar/baz/quux/aaa.py + touch :bar/baz/quux/bbbb.py +$MPREMOTE resume mkdir :bar/baz/quux/xen + touch :bar/baz/quux/xen/aaa.py + +$MPREMOTE resume tree + +echo ----- +echo single path +$MPREMOTE resume tree :foo + +echo ----- +echo "multiple paths" +$MPREMOTE resume tree :foo :bar + +echo ----- +echo "subtree" +$MPREMOTE resume tree bar/baz + +echo ----- +echo mountpoint +$MPREMOTE resume tree :/ramdisk + +echo ----- +echo non-existent folder : error +$MPREMOTE resume tree :not_there || echo "expect error: $?" + +echo ----- +echo file : error +$MPREMOTE resume tree :a.py || echo "expect error: $?" + +echo ----- +echo "tree -s :" +mkdir -p "${TMP}/data" +dd if=/dev/zero of="${TMP}/data/file1.txt" bs=1 count=20 > /dev/null 2>&1 +dd if=/dev/zero of="${TMP}/data/file2.txt" bs=1 count=204 > /dev/null 2>&1 +dd if=/dev/zero of="${TMP}/data/file3.txt" bs=1 count=1096 > /dev/null 2>&1 +dd if=/dev/zero of="${TMP}/data/file4.txt" bs=1 count=2192 > /dev/null 2>&1 + +$MPREMOTE resume cp -r "${TMP}/data" : +$MPREMOTE resume tree -s : +echo ----- +echo "tree -s" +$MPREMOTE resume tree -s +echo ----- +$MPREMOTE resume tree --human : +echo ----- +$MPREMOTE resume tree -s --human : || echo "expect error: $?" +echo ----- + diff --git a/tools/mpremote/tests/test_fs_tree.sh.exp b/tools/mpremote/tests/test_fs_tree.sh.exp new file mode 100644 index 0000000000000..9a67883b1c87f --- /dev/null +++ b/tools/mpremote/tests/test_fs_tree.sh.exp @@ -0,0 +1,225 @@ +----- +ls : +----- +empty tree +tree : +:/ramdisk +----- +touch :a.py +touch :b.py +mkdir :foo +touch :foo/aa.py +touch :foo/ba.py +small tree - : +tree : +:/ramdisk +ā”œā”€ā”€ a.py +ā”œā”€ā”€ b.py +└── foo + ā”œā”€ā”€ aa.py + └── ba.py +----- +no path +tree : +:/ramdisk +ā”œā”€ā”€ a.py +ā”œā”€ā”€ b.py +└── foo + ā”œā”€ā”€ aa.py + └── ba.py +----- +path = '.' +tree :. +:/ramdisk +ā”œā”€ā”€ a.py +ā”œā”€ā”€ b.py +└── foo + ā”œā”€ā”€ aa.py + └── ba.py +----- +path = ':.' +tree :. +:/ramdisk +ā”œā”€ā”€ a.py +ā”œā”€ā”€ b.py +└── foo + ā”œā”€ā”€ aa.py + └── ba.py +----- +multiple trees +mkdir :bar +touch :bar/aaa.py +touch :bar/bbbb.py +mkdir :bar/baz +touch :bar/baz/aaa.py +touch :bar/baz/bbbb.py +mkdir :bar/baz/quux +touch :bar/baz/quux/aaa.py +touch :bar/baz/quux/bbbb.py +mkdir :bar/baz/quux/xen +touch :bar/baz/quux/xen/aaa.py +tree : +:/ramdisk +ā”œā”€ā”€ a.py +ā”œā”€ā”€ b.py +ā”œā”€ā”€ bar +│ ā”œā”€ā”€ aaa.py +│ ā”œā”€ā”€ baz +│ │ ā”œā”€ā”€ aaa.py +│ │ ā”œā”€ā”€ bbbb.py +│ │ └── quux +│ │ ā”œā”€ā”€ aaa.py +│ │ ā”œā”€ā”€ bbbb.py +│ │ └── xen +│ │ └── aaa.py +│ └── bbbb.py +└── foo + ā”œā”€ā”€ aa.py + └── ba.py +----- +single path +tree :foo +:foo +ā”œā”€ā”€ aa.py +└── ba.py +----- +multiple paths +tree :foo +:foo +ā”œā”€ā”€ aa.py +└── ba.py +tree :bar +:bar +ā”œā”€ā”€ aaa.py +ā”œā”€ā”€ baz +│ ā”œā”€ā”€ aaa.py +│ ā”œā”€ā”€ bbbb.py +│ └── quux +│ ā”œā”€ā”€ aaa.py +│ ā”œā”€ā”€ bbbb.py +│ └── xen +│ └── aaa.py +└── bbbb.py +----- +subtree +tree :bar/baz +:bar/baz +ā”œā”€ā”€ aaa.py +ā”œā”€ā”€ bbbb.py +└── quux + ā”œā”€ā”€ aaa.py + ā”œā”€ā”€ bbbb.py + └── xen + └── aaa.py +----- +mountpoint +tree :/ramdisk +:/ramdisk +ā”œā”€ā”€ a.py +ā”œā”€ā”€ b.py +ā”œā”€ā”€ bar +│ ā”œā”€ā”€ aaa.py +│ ā”œā”€ā”€ baz +│ │ ā”œā”€ā”€ aaa.py +│ │ ā”œā”€ā”€ bbbb.py +│ │ └── quux +│ │ ā”œā”€ā”€ aaa.py +│ │ ā”œā”€ā”€ bbbb.py +│ │ └── xen +│ │ └── aaa.py +│ └── bbbb.py +└── foo + ā”œā”€ā”€ aa.py + └── ba.py +----- +non-existent folder : error +tree :not_there +mpremote: tree: 'not_there' is not a directory +expect error: 1 +----- +file : error +tree :a.py +mpremote: tree: 'a.py' is not a directory +expect error: 1 +----- +tree -s : +cp ${TMP}/data : +tree : +:/ramdisk +ā”œā”€ā”€ [ 0] a.py +ā”œā”€ā”€ [ 0] b.py +ā”œā”€ā”€ bar +│ ā”œā”€ā”€ [ 0] aaa.py +│ ā”œā”€ā”€ baz +│ │ ā”œā”€ā”€ [ 0] aaa.py +│ │ ā”œā”€ā”€ [ 0] bbbb.py +│ │ └── quux +│ │ ā”œā”€ā”€ [ 0] aaa.py +│ │ ā”œā”€ā”€ [ 0] bbbb.py +│ │ └── xen +│ │ └── [ 0] aaa.py +│ └── [ 0] bbbb.py +ā”œā”€ā”€ data +│ ā”œā”€ā”€ [ 20] file1.txt +│ ā”œā”€ā”€ [ 204] file2.txt +│ ā”œā”€ā”€ [ 1096] file3.txt +│ └── [ 2192] file4.txt +└── foo + ā”œā”€ā”€ [ 0] aa.py + └── [ 0] ba.py +----- +tree -s +tree : +:/ramdisk +ā”œā”€ā”€ [ 0] a.py +ā”œā”€ā”€ [ 0] b.py +ā”œā”€ā”€ bar +│ ā”œā”€ā”€ [ 0] aaa.py +│ ā”œā”€ā”€ baz +│ │ ā”œā”€ā”€ [ 0] aaa.py +│ │ ā”œā”€ā”€ [ 0] bbbb.py +│ │ └── quux +│ │ ā”œā”€ā”€ [ 0] aaa.py +│ │ ā”œā”€ā”€ [ 0] bbbb.py +│ │ └── xen +│ │ └── [ 0] aaa.py +│ └── [ 0] bbbb.py +ā”œā”€ā”€ data +│ ā”œā”€ā”€ [ 20] file1.txt +│ ā”œā”€ā”€ [ 204] file2.txt +│ ā”œā”€ā”€ [ 1096] file3.txt +│ └── [ 2192] file4.txt +└── foo + ā”œā”€ā”€ [ 0] aa.py + └── [ 0] ba.py +----- +tree : +:/ramdisk +ā”œā”€ā”€ [ 0] a.py +ā”œā”€ā”€ [ 0] b.py +ā”œā”€ā”€ bar +│ ā”œā”€ā”€ [ 0] aaa.py +│ ā”œā”€ā”€ baz +│ │ ā”œā”€ā”€ [ 0] aaa.py +│ │ ā”œā”€ā”€ [ 0] bbbb.py +│ │ └── quux +│ │ ā”œā”€ā”€ [ 0] aaa.py +│ │ ā”œā”€ā”€ [ 0] bbbb.py +│ │ └── xen +│ │ └── [ 0] aaa.py +│ └── [ 0] bbbb.py +ā”œā”€ā”€ data +│ ā”œā”€ā”€ [ 20] file1.txt +│ ā”œā”€ā”€ [ 204] file2.txt +│ ā”œā”€ā”€ [ 1.1K] file3.txt +│ └── [ 2.1K] file4.txt +└── foo + ā”œā”€ā”€ [ 0] aa.py + └── [ 0] ba.py +----- +usage: fs [--help] [--recursive | --no-recursive] [--force | --no-force] + [--verbose | --no-verbose] [--size | --human] + command path [path ...] ... +fs: error: argument --human/-h: not allowed with argument --size/-s +expect error: 2 +----- From e39243c3820c193b97f34ff033c9a598e40277a7 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Fri, 9 May 2025 20:30:10 +0200 Subject: [PATCH 127/154] docs/reference/mpremote: Document the 'fs tree' command. Signed-off-by: Jos Verlinde --- docs/reference/mpremote.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/reference/mpremote.rst b/docs/reference/mpremote.rst index 32ca5c246a7a4..bee008c637319 100644 --- a/docs/reference/mpremote.rst +++ b/docs/reference/mpremote.rst @@ -234,6 +234,7 @@ The full list of supported commands are: - ``rmdir `` to remove directories on the device - ``touch `` to create the files (if they don't already exist) - ``sha256sum `` to calculate the SHA256 sum of files + - ``tree [-vsh] `` to print a tree of the given directories The ``cp`` command uses a convention where a leading ``:`` represents a remote path. Without a leading ``:`` means a local path. This is based on the @@ -264,6 +265,13 @@ The full list of supported commands are: There is no supported way to undelete files removed by ``mpremote rm -r :``. Please use with caution. + The ``tree`` command will print a tree of the given directories. + Using the ``--size/-s`` option will print the size of each file, or use + ``--human/-h`` to use a more human readable format. + Note: Directory size is only printed when a non-zero size is reported by the device's filesystem. + The ``-v`` option can be used to include the name of the serial device in + the output. + All other commands implicitly assume the path is a remote path, but the ``:`` can be optionally used for clarity. From 7a55cb6b364fdbc2f3291456643bd640ba566ec9 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 13 May 2025 17:16:11 +1000 Subject: [PATCH 128/154] tests/run-tests.py: Add list of passed/skipped tests to _result.json. The output `_result.json` file generated by `run-tests.py` currently contains a list of failed tests. This commit adds to the output a list of passed and skipped tests, and so now provides full information about which tests were run and what their results were. Signed-off-by: Damien George --- tests/run-tests.py | 48 ++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index ac411a0be6ada..9294c7e636fe8 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -605,7 +605,7 @@ def run_script_on_remote_target(self, args, test_file, is_special): def run_tests(pyb, tests, args, result_dir, num_threads=1): test_count = ThreadSafeCounter() testcase_count = ThreadSafeCounter() - passed_count = ThreadSafeCounter() + passed_tests = ThreadSafeCounter([]) failed_tests = ThreadSafeCounter([]) skipped_tests = ThreadSafeCounter([]) @@ -896,7 +896,7 @@ def run_one_test(test_file): if skip_it: print("skip ", test_file) - skipped_tests.append(test_name) + skipped_tests.append((test_name, test_file)) return # Run the test on the MicroPython target. @@ -911,7 +911,7 @@ def run_one_test(test_file): # start-up code (eg boot.py) when preparing to run the next test. pyb.read_until(1, b"raw REPL; CTRL-B to exit\r\n") print("skip ", test_file) - skipped_tests.append(test_name) + skipped_tests.append((test_name, test_file)) return # Look at the output of the test to see if unittest was used. @@ -994,7 +994,7 @@ def run_one_test(test_file): # Print test summary, update counters, and save .exp/.out files if needed. if test_passed: print("pass ", test_file, extra_info) - passed_count.increment() + passed_tests.append((test_name, test_file)) rm_f(filename_expected) rm_f(filename_mupy) else: @@ -1035,17 +1035,30 @@ def run_one_test(test_file): print(line) sys.exit(1) + passed_tests = sorted(passed_tests.value) + skipped_tests = sorted(skipped_tests.value) + failed_tests = sorted(failed_tests.value) + print( "{} tests performed ({} individual testcases)".format( test_count.value, testcase_count.value ) ) - print("{} tests passed".format(passed_count.value)) + print("{} tests passed".format(len(passed_tests))) - skipped_tests = sorted(skipped_tests.value) if len(skipped_tests) > 0: - print("{} tests skipped: {}".format(len(skipped_tests), " ".join(skipped_tests))) - failed_tests = sorted(failed_tests.value) + print( + "{} tests skipped: {}".format( + len(skipped_tests), " ".join(test[0] for test in skipped_tests) + ) + ) + + if len(failed_tests) > 0: + print( + "{} tests failed: {}".format( + len(failed_tests), " ".join(test[0] for test in failed_tests) + ) + ) # Serialize regex added by append_filter. def to_json(obj): @@ -1055,21 +1068,18 @@ def to_json(obj): with open(os.path.join(result_dir, RESULTS_FILE), "w") as f: json.dump( - {"args": vars(args), "failed_tests": [test[1] for test in failed_tests]}, + { + "args": vars(args), + "passed_tests": [test[1] for test in passed_tests], + "skipped_tests": [test[1] for test in skipped_tests], + "failed_tests": [test[1] for test in failed_tests], + }, f, default=to_json, ) - if len(failed_tests) > 0: - print( - "{} tests failed: {}".format( - len(failed_tests), " ".join(test[0] for test in failed_tests) - ) - ) - return False - - # all tests succeeded - return True + # Return True only if all tests succeeded. + return len(failed_tests) == 0 class append_filter(argparse.Action): From 9ef16b466bf980a3b3e0980e7d010b0b9130569b Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 17 Apr 2025 16:56:11 +0200 Subject: [PATCH 129/154] extmod/modjson: Detect unterminated composite entities. This commit makes the JSON parser raise an exception when handling objects or arrays whose declaration is incomplete, as in missing the closing marker (brace or bracket) and if the missing marker would have been the last non-whitespace character in the incoming string. Since CPython's JSON parser would raise an exception in such a case, unlike MicroPython's, this commit aligns MicroPython's behaviour with CPython. This commit fixes issue #17141. Signed-off-by: Alessandro Gatti --- extmod/modjson.c | 3 ++- tests/extmod/json_loads.py | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/extmod/modjson.c b/extmod/modjson.c index e655a02bc00ef..11aedd1983380 100644 --- a/extmod/modjson.c +++ b/extmod/modjson.c @@ -160,7 +160,8 @@ static mp_obj_t mod_json_load(mp_obj_t stream_obj) { for (;;) { cont: if (S_END(s)) { - break; + // Input finished abruptly in the middle of a composite entity. + goto fail; } mp_obj_t next = MP_OBJ_NULL; bool enter = false; diff --git a/tests/extmod/json_loads.py b/tests/extmod/json_loads.py index f9073c121e2ef..095e67d740b9d 100644 --- a/tests/extmod/json_loads.py +++ b/tests/extmod/json_loads.py @@ -71,3 +71,27 @@ def my_print(o): my_print(json.loads("[null] a")) except ValueError: print("ValueError") + +# incomplete object declaration +try: + my_print(json.loads('{"a":0,')) +except ValueError: + print("ValueError") + +# incomplete nested array declaration +try: + my_print(json.loads('{"a":0, [')) +except ValueError: + print("ValueError") + +# incomplete array declaration +try: + my_print(json.loads('[0,')) +except ValueError: + print("ValueError") + +# incomplete nested object declaration +try: + my_print(json.loads('[0, {"a":0, ')) +except ValueError: + print("ValueError") From 186caf9f0326c9d61494a7d5c6d0408c0fef8485 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 15 May 2025 11:26:17 +1000 Subject: [PATCH 130/154] extmod/network_cyw43: Disconnect STA if making inactive. esp32 port will disconnect if active(0) is called on a STA interface, but rp2 port stays associated without this change. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- extmod/network_cyw43.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extmod/network_cyw43.c b/extmod/network_cyw43.c index 1b1b10b40741a..9ebfa904db568 100644 --- a/extmod/network_cyw43.c +++ b/extmod/network_cyw43.c @@ -143,6 +143,9 @@ static mp_obj_t network_cyw43_active(size_t n_args, const mp_obj_t *args) { return mp_obj_new_bool(if_active[self->itf]); } else { bool value = mp_obj_is_true(args[1]); + if (!value && self->itf == CYW43_ITF_STA) { + cyw43_wifi_leave(self->cyw, self->itf); + } cyw43_wifi_set_up(self->cyw, self->itf, value, get_country_code()); if_active[self->itf] = value; return mp_const_none; From 1d37caa3679c86ef4fe43d2f9e6ac13074498433 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 17 Apr 2025 00:27:28 +0200 Subject: [PATCH 131/154] py/emitnative: Improve Viper register-indexed code for Arm. This commit lets the Viper code generator use optimised code sequences for register-indexed load and store operations when generating Arm code. The existing code defaulted to generic multi-operations code sequences for Arm code on most cases. Now optimised implementations are provided for register-indexed loads and stores of all data sizes, taking at most two machine opcodes for each operation. Signed-off-by: Alessandro Gatti --- py/asmarm.c | 16 ++++++++++++++++ py/asmarm.h | 6 ++++++ py/emitnative.c | 14 ++++++++++++-- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/py/asmarm.c b/py/asmarm.c index 6fa751b32eb7c..d304567882cef 100644 --- a/py/asmarm.c +++ b/py/asmarm.c @@ -343,6 +343,12 @@ void asm_arm_ldrh_reg_reg(asm_arm_t *as, uint rd, uint rn) { emit_al(as, 0x1d000b0 | (rn << 16) | (rd << 12)); } +void asm_arm_ldrh_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn) { + // ldrh doesn't support scaled register index + emit_al(as, 0x1a00080 | (ASM_ARM_REG_R8 << 12) | rn); // mov r8, rn, lsl #1 + emit_al(as, 0x19000b0 | (rm << 16) | (rd << 12) | ASM_ARM_REG_R8); // ldrh rd, [rm, r8]; +} + void asm_arm_ldrh_reg_reg_offset(asm_arm_t *as, uint rd, uint rn, uint byte_offset) { if (byte_offset < 0x100) { // ldrh rd, [rn, #off] @@ -360,6 +366,16 @@ void asm_arm_ldrb_reg_reg(asm_arm_t *as, uint rd, uint rn) { emit_al(as, 0x5d00000 | (rn << 16) | (rd << 12)); } +void asm_arm_ldrb_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn) { + // ldrb rd, [rm, rn] + emit_al(as, 0x7d00000 | (rm << 16) | (rd << 12) | rn); +} + +void asm_arm_ldr_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn) { + // ldr rd, [rm, rn, lsl #2] + emit_al(as, 0x7900100 | (rm << 16) | (rd << 12) | rn); +} + void asm_arm_str_reg_reg(asm_arm_t *as, uint rd, uint rm, uint byte_offset) { // str rd, [rm, #off] emit_al(as, 0x5800000 | (rm << 16) | (rd << 12) | byte_offset); diff --git a/py/asmarm.h b/py/asmarm.h index 4a4253aef6878..20b4757d21fa9 100644 --- a/py/asmarm.h +++ b/py/asmarm.h @@ -116,6 +116,12 @@ void asm_arm_ldrb_reg_reg(asm_arm_t *as, uint rd, uint rn); void asm_arm_str_reg_reg(asm_arm_t *as, uint rd, uint rm, uint byte_offset); void asm_arm_strh_reg_reg(asm_arm_t *as, uint rd, uint rm); void asm_arm_strb_reg_reg(asm_arm_t *as, uint rd, uint rm); + +// load from array +void asm_arm_ldr_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn); +void asm_arm_ldrh_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn); +void asm_arm_ldrb_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn); + // store to array void asm_arm_str_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn); void asm_arm_strh_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn); diff --git a/py/emitnative.c b/py/emitnative.c index 1aab0a9eb7865..2fb4bdb4285a9 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -1638,6 +1638,10 @@ static void emit_native_load_subscr(emit_t *emit) { switch (vtype_base) { case VTYPE_PTR8: { // pointer to 8-bit memory + #if N_ARM + asm_arm_ldrb_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); + break; + #endif // TODO optimise to use thumb ldrb r1, [r2, r3] ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_LOAD8_REG_REG(emit->as, REG_RET, REG_ARG_1); // store value to (base+index) @@ -1645,7 +1649,10 @@ static void emit_native_load_subscr(emit_t *emit) { } case VTYPE_PTR16: { // pointer to 16-bit memory - #if N_XTENSA || N_XTENSAWIN + #if N_ARM + asm_arm_ldrh_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); + break; + #elif N_XTENSA || N_XTENSAWIN asm_xtensa_op_addx2(emit->as, REG_ARG_1, reg_index, REG_ARG_1); asm_xtensa_op_l16ui(emit->as, REG_RET, REG_ARG_1, 0); break; @@ -1657,7 +1664,10 @@ static void emit_native_load_subscr(emit_t *emit) { } case VTYPE_PTR32: { // pointer to word-size memory - #if N_RV32 + #if N_ARM + asm_arm_ldr_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); + break; + #elif N_RV32 asm_rv32_opcode_slli(emit->as, REG_TEMP2, reg_index, 2); asm_rv32_opcode_cadd(emit->as, REG_ARG_1, REG_TEMP2); asm_rv32_opcode_lw(emit->as, REG_RET, REG_ARG_1, 0); From 04c6b99cb941cdc00822f17fe530a63639359019 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 7 May 2025 22:21:30 +0200 Subject: [PATCH 132/154] py/emitnative: Improve Viper register-indexed code for Thumb. This commit lets the Viper code generator use optimised code sequence for register-indexed load and store operations when generating Thumb code. Register-indexed load and store operations for Thumb now can take at most two machine opcodes for halfword and word values, and just a single machine opcode for byte values. The original implementation could generate up to four opcodes in the worst case (dealing with word values). Signed-off-by: Alessandro Gatti --- py/asmthumb.c | 20 ++++++++++++++++++++ py/asmthumb.h | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ py/emitnative.c | 18 ++++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/py/asmthumb.c b/py/asmthumb.c index 420815e80269a..73684c0f5f060 100644 --- a/py/asmthumb.c +++ b/py/asmthumb.c @@ -491,6 +491,26 @@ void asm_thumb_ldrh_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint r } } +void asm_thumb_ldrh_reg_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_base, uint reg_index) { + asm_thumb_lsl_rlo_rlo_i5(as, reg_index, reg_index, 1); + asm_thumb_ldrh_rlo_rlo_rlo(as, reg_dest, reg_base, reg_index); +} + +void asm_thumb_ldr_reg_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_base, uint reg_index) { + asm_thumb_lsl_rlo_rlo_i5(as, reg_index, reg_index, 2); + asm_thumb_ldr_rlo_rlo_rlo(as, reg_dest, reg_base, reg_index); +} + +void asm_thumb_strh_reg_reg_reg(asm_thumb_t *as, uint reg_val, uint reg_base, uint reg_index) { + asm_thumb_lsl_rlo_rlo_i5(as, reg_index, reg_index, 1); + asm_thumb_strh_rlo_rlo_rlo(as, reg_val, reg_base, reg_index); +} + +void asm_thumb_str_reg_reg_reg(asm_thumb_t *as, uint reg_val, uint reg_base, uint reg_index) { + asm_thumb_lsl_rlo_rlo_i5(as, reg_index, reg_index, 2); + asm_thumb_str_rlo_rlo_rlo(as, reg_val, reg_base, reg_index); +} + // this could be wrong, because it should have a range of +/- 16MiB... #define OP_BW_HI(byte_offset) (0xf000 | (((byte_offset) >> 12) & 0x07ff)) #define OP_BW_LO(byte_offset) (0xb800 | (((byte_offset) >> 1) & 0x07ff)) diff --git a/py/asmthumb.h b/py/asmthumb.h index a9e68d7adbbea..f7cd52fb40701 100644 --- a/py/asmthumb.h +++ b/py/asmthumb.h @@ -251,6 +251,50 @@ static inline void asm_thumb_bx_reg(asm_thumb_t *as, uint r_src) { asm_thumb_format_5(as, ASM_THUMB_FORMAT_5_BX, 0, r_src); } +// FORMAT 7: load/store with register offset +// FORMAT 8: load/store sign-extended byte/halfword + +#define ASM_THUMB_FORMAT_7_LDR (0x5800) +#define ASM_THUMB_FORMAT_7_STR (0x5000) +#define ASM_THUMB_FORMAT_7_WORD_TRANSFER (0x0000) +#define ASM_THUMB_FORMAT_7_BYTE_TRANSFER (0x0400) +#define ASM_THUMB_FORMAT_8_LDRH (0x5A00) +#define ASM_THUMB_FORMAT_8_STRH (0x5200) + +#define ASM_THUMB_FORMAT_7_8_ENCODE(op, rlo_dest, rlo_base, rlo_index) \ + ((op) | ((rlo_index) << 6) | ((rlo_base) << 3) | ((rlo_dest))) + +static inline void asm_thumb_format_7_8(asm_thumb_t *as, uint op, uint rlo_dest, uint rlo_base, uint rlo_index) { + assert(rlo_dest < ASM_THUMB_REG_R8); + assert(rlo_base < ASM_THUMB_REG_R8); + assert(rlo_index < ASM_THUMB_REG_R8); + asm_thumb_op16(as, ASM_THUMB_FORMAT_7_8_ENCODE(op, rlo_dest, rlo_base, rlo_index)); +} + +static inline void asm_thumb_ldrb_rlo_rlo_rlo(asm_thumb_t *as, uint rlo_dest, uint rlo_base, uint rlo_index) { + asm_thumb_format_7_8(as, ASM_THUMB_FORMAT_7_LDR | ASM_THUMB_FORMAT_7_BYTE_TRANSFER, rlo_dest, rlo_base, rlo_index); +} + +static inline void asm_thumb_ldrh_rlo_rlo_rlo(asm_thumb_t *as, uint rlo_dest, uint rlo_base, uint rlo_index) { + asm_thumb_format_7_8(as, ASM_THUMB_FORMAT_8_LDRH, rlo_dest, rlo_base, rlo_index); +} + +static inline void asm_thumb_ldr_rlo_rlo_rlo(asm_thumb_t *as, uint rlo_dest, uint rlo_base, uint rlo_index) { + asm_thumb_format_7_8(as, ASM_THUMB_FORMAT_7_LDR | ASM_THUMB_FORMAT_7_WORD_TRANSFER, rlo_dest, rlo_base, rlo_index); +} + +static inline void asm_thumb_strb_rlo_rlo_rlo(asm_thumb_t *as, uint rlo_src, uint rlo_base, uint rlo_index) { + asm_thumb_format_7_8(as, ASM_THUMB_FORMAT_7_STR | ASM_THUMB_FORMAT_7_BYTE_TRANSFER, rlo_src, rlo_base, rlo_index); +} + +static inline void asm_thumb_strh_rlo_rlo_rlo(asm_thumb_t *as, uint rlo_dest, uint rlo_base, uint rlo_index) { + asm_thumb_format_7_8(as, ASM_THUMB_FORMAT_8_STRH, rlo_dest, rlo_base, rlo_index); +} + +static inline void asm_thumb_str_rlo_rlo_rlo(asm_thumb_t *as, uint rlo_src, uint rlo_base, uint rlo_index) { + asm_thumb_format_7_8(as, ASM_THUMB_FORMAT_7_STR | ASM_THUMB_FORMAT_7_WORD_TRANSFER, rlo_src, rlo_base, rlo_index); +} + // FORMAT 9: load/store with immediate offset // For word transfers the offset must be aligned, and >>2 @@ -341,6 +385,11 @@ void asm_thumb_mov_reg_pcrel(asm_thumb_t *as, uint rlo_dest, uint label); void asm_thumb_ldr_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint reg_base, uint word_offset); // convenience void asm_thumb_ldrh_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint reg_base, uint uint16_offset); // convenience +void asm_thumb_ldrh_reg_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_base, uint reg_index); +void asm_thumb_ldr_reg_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_base, uint reg_index); +void asm_thumb_strh_reg_reg_reg(asm_thumb_t *as, uint reg_val, uint reg_base, uint reg_index); +void asm_thumb_str_reg_reg_reg(asm_thumb_t *as, uint reg_val, uint reg_base, uint reg_index); + void asm_thumb_b_label(asm_thumb_t *as, uint label); // convenience: picks narrow or wide branch void asm_thumb_bcc_label(asm_thumb_t *as, int cc, uint label); // convenience: picks narrow or wide branch void asm_thumb_bl_ind(asm_thumb_t *as, uint fun_id, uint reg_temp); // convenience diff --git a/py/emitnative.c b/py/emitnative.c index 2fb4bdb4285a9..088e1e7ae0e11 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -1641,6 +1641,9 @@ static void emit_native_load_subscr(emit_t *emit) { #if N_ARM asm_arm_ldrb_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); break; + #elif N_THUMB + asm_thumb_ldrb_rlo_rlo_rlo(emit->as, REG_RET, REG_ARG_1, reg_index); + break; #endif // TODO optimise to use thumb ldrb r1, [r2, r3] ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base @@ -1652,6 +1655,9 @@ static void emit_native_load_subscr(emit_t *emit) { #if N_ARM asm_arm_ldrh_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); break; + #elif N_THUMB + asm_thumb_ldrh_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); + break; #elif N_XTENSA || N_XTENSAWIN asm_xtensa_op_addx2(emit->as, REG_ARG_1, reg_index, REG_ARG_1); asm_xtensa_op_l16ui(emit->as, REG_RET, REG_ARG_1, 0); @@ -1667,6 +1673,9 @@ static void emit_native_load_subscr(emit_t *emit) { #if N_ARM asm_arm_ldr_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); break; + #elif N_THUMB + asm_thumb_ldr_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); + break; #elif N_RV32 asm_rv32_opcode_slli(emit->as, REG_TEMP2, reg_index, 2); asm_rv32_opcode_cadd(emit->as, REG_ARG_1, REG_TEMP2); @@ -1944,6 +1953,9 @@ static void emit_native_store_subscr(emit_t *emit) { #if N_ARM asm_arm_strb_reg_reg_reg(emit->as, reg_value, REG_ARG_1, reg_index); break; + #elif N_THUMB + asm_thumb_strb_rlo_rlo_rlo(emit->as, reg_value, REG_ARG_1, reg_index); + break; #endif ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_STORE8_REG_REG(emit->as, reg_value, REG_ARG_1); // store value to (base+index) @@ -1954,6 +1966,9 @@ static void emit_native_store_subscr(emit_t *emit) { #if N_ARM asm_arm_strh_reg_reg_reg(emit->as, reg_value, REG_ARG_1, reg_index); break; + #elif N_THUMB + asm_thumb_strh_reg_reg_reg(emit->as, reg_value, REG_ARG_1, reg_index); + break; #elif N_XTENSA || N_XTENSAWIN asm_xtensa_op_addx2(emit->as, REG_ARG_1, reg_index, REG_ARG_1); asm_xtensa_op_s16i(emit->as, reg_value, REG_ARG_1, 0); @@ -1969,6 +1984,9 @@ static void emit_native_store_subscr(emit_t *emit) { #if N_ARM asm_arm_str_reg_reg_reg(emit->as, reg_value, REG_ARG_1, reg_index); break; + #elif N_THUMB + asm_thumb_str_reg_reg_reg(emit->as, reg_value, REG_ARG_1, reg_index); + break; #elif N_RV32 asm_rv32_opcode_slli(emit->as, REG_TEMP2, reg_index, 2); asm_rv32_opcode_cadd(emit->as, REG_ARG_1, REG_TEMP2); From b6d269ee32026c7380fdb6ebcbc7e50e84239c12 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 7 May 2025 22:41:33 +0200 Subject: [PATCH 133/154] py/emitnative: Refactor Viper register-indexed load/stores. This commit cleans up the Viper code generation blocks for register-indexed load and store operations. An attempt is made to simplify the code in the common code generator code block, by moving architecture-specific code to the appropriate native generation backends whenever possible. This should make that specific bit of code in the Viper generator clearer and easier to maintain in the long term. To achieve this, six generic assembler meta-opcodes have been introduced, named `ASM_{LOAD,STORE}{8,16,32}_REG_REG_REG`. A platform-independent implementation for those operations is provided, so backends that cannot emit a shorter sequence for the requested operation or are fine with the platform-independent implementation can just not provide said meta-opcodes. Signed-off-by: Alessandro Gatti --- py/asmarm.h | 7 ++++ py/asmrv32.h | 24 +++++++++++++ py/asmthumb.c | 20 ----------- py/asmthumb.h | 28 ++++++++++++--- py/asmxtensa.h | 20 +++++++++++ py/emitnative.c | 94 +++++++++++++------------------------------------ 6 files changed, 98 insertions(+), 95 deletions(-) diff --git a/py/asmarm.h b/py/asmarm.h index 20b4757d21fa9..77c700ff0b87b 100644 --- a/py/asmarm.h +++ b/py/asmarm.h @@ -221,6 +221,13 @@ void asm_arm_bx_reg(asm_arm_t *as, uint reg_src); #define ASM_STORE16_REG_REG(as, reg_value, reg_base) asm_arm_strh_reg_reg((as), (reg_value), (reg_base)) #define ASM_STORE32_REG_REG(as, reg_value, reg_base) asm_arm_str_reg_reg((as), (reg_value), (reg_base), 0) +#define ASM_LOAD8_REG_REG_REG(as, reg_dest, reg_base, reg_index) asm_arm_ldrb_reg_reg_reg((as), (reg_dest), (reg_base), (reg_index)) +#define ASM_LOAD16_REG_REG_REG(as, reg_dest, reg_base, reg_index) asm_arm_ldrh_reg_reg_reg((as), (reg_dest), (reg_base), (reg_index)) +#define ASM_LOAD32_REG_REG_REG(as, reg_dest, reg_base, reg_index) asm_arm_ldr_reg_reg_reg((as), (reg_dest), (reg_base), (reg_index)) +#define ASM_STORE8_REG_REG_REG(as, reg_val, reg_base, reg_index) asm_arm_strb_reg_reg_reg((as), (reg_val), (reg_base), (reg_index)) +#define ASM_STORE16_REG_REG_REG(as, reg_val, reg_base, reg_index) asm_arm_strh_reg_reg_reg((as), (reg_val), (reg_base), (reg_index)) +#define ASM_STORE32_REG_REG_REG(as, reg_val, reg_base, reg_index) asm_arm_str_reg_reg_reg((as), (reg_val), (reg_base), (reg_index)) + #endif // GENERIC_ASM_API #endif // MICROPY_INCLUDED_PY_ASMARM_H diff --git a/py/asmrv32.h b/py/asmrv32.h index b09f48eb12f66..1cbdc88720884 100644 --- a/py/asmrv32.h +++ b/py/asmrv32.h @@ -758,6 +758,30 @@ void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t source, mp_ #define ASM_SUB_REG_REG(state, rd, rs) asm_rv32_opcode_sub(state, rd, rd, rs) #define ASM_XOR_REG_REG(state, rd, rs) asm_rv32_emit_optimised_xor(state, rd, rs) #define ASM_CLR_REG(state, rd) +#define ASM_LOAD16_REG_REG_REG(state, rd, rs1, rs2) \ + do { \ + asm_rv32_opcode_slli(state, rs2, rs2, 1); \ + asm_rv32_opcode_cadd(state, rs1, rs2); \ + asm_rv32_opcode_lhu(state, rd, rs1, 0); \ + } while (0) +#define ASM_LOAD32_REG_REG_REG(state, rd, rs1, rs2) \ + do { \ + asm_rv32_opcode_slli(state, rs2, rs2, 2); \ + asm_rv32_opcode_cadd(state, rs1, rs2); \ + asm_rv32_opcode_lw(state, rd, rs1, 0); \ + } while (0) +#define ASM_STORE16_REG_REG_REG(state, rd, rs1, rs2) \ + do { \ + asm_rv32_opcode_slli(state, rs2, rs2, 1); \ + asm_rv32_opcode_cadd(state, rs1, rs2); \ + asm_rv32_opcode_sh(state, rd, rs1, 0); \ + } while (0) +#define ASM_STORE32_REG_REG_REG(state, rd, rs1, rs2) \ + do { \ + asm_rv32_opcode_slli(state, rs2, rs2, 2); \ + asm_rv32_opcode_cadd(state, rs1, rs2); \ + asm_rv32_opcode_sw(state, rd, rs1, 0); \ + } while (0) #endif diff --git a/py/asmthumb.c b/py/asmthumb.c index 73684c0f5f060..420815e80269a 100644 --- a/py/asmthumb.c +++ b/py/asmthumb.c @@ -491,26 +491,6 @@ void asm_thumb_ldrh_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint r } } -void asm_thumb_ldrh_reg_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_base, uint reg_index) { - asm_thumb_lsl_rlo_rlo_i5(as, reg_index, reg_index, 1); - asm_thumb_ldrh_rlo_rlo_rlo(as, reg_dest, reg_base, reg_index); -} - -void asm_thumb_ldr_reg_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_base, uint reg_index) { - asm_thumb_lsl_rlo_rlo_i5(as, reg_index, reg_index, 2); - asm_thumb_ldr_rlo_rlo_rlo(as, reg_dest, reg_base, reg_index); -} - -void asm_thumb_strh_reg_reg_reg(asm_thumb_t *as, uint reg_val, uint reg_base, uint reg_index) { - asm_thumb_lsl_rlo_rlo_i5(as, reg_index, reg_index, 1); - asm_thumb_strh_rlo_rlo_rlo(as, reg_val, reg_base, reg_index); -} - -void asm_thumb_str_reg_reg_reg(asm_thumb_t *as, uint reg_val, uint reg_base, uint reg_index) { - asm_thumb_lsl_rlo_rlo_i5(as, reg_index, reg_index, 2); - asm_thumb_str_rlo_rlo_rlo(as, reg_val, reg_base, reg_index); -} - // this could be wrong, because it should have a range of +/- 16MiB... #define OP_BW_HI(byte_offset) (0xf000 | (((byte_offset) >> 12) & 0x07ff)) #define OP_BW_LO(byte_offset) (0xb800 | (((byte_offset) >> 1) & 0x07ff)) diff --git a/py/asmthumb.h b/py/asmthumb.h index f7cd52fb40701..f62f7dd8d9e73 100644 --- a/py/asmthumb.h +++ b/py/asmthumb.h @@ -385,11 +385,6 @@ void asm_thumb_mov_reg_pcrel(asm_thumb_t *as, uint rlo_dest, uint label); void asm_thumb_ldr_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint reg_base, uint word_offset); // convenience void asm_thumb_ldrh_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint reg_base, uint uint16_offset); // convenience -void asm_thumb_ldrh_reg_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_base, uint reg_index); -void asm_thumb_ldr_reg_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_base, uint reg_index); -void asm_thumb_strh_reg_reg_reg(asm_thumb_t *as, uint reg_val, uint reg_base, uint reg_index); -void asm_thumb_str_reg_reg_reg(asm_thumb_t *as, uint reg_val, uint reg_base, uint reg_index); - void asm_thumb_b_label(asm_thumb_t *as, uint label); // convenience: picks narrow or wide branch void asm_thumb_bcc_label(asm_thumb_t *as, int cc, uint label); // convenience: picks narrow or wide branch void asm_thumb_bl_ind(asm_thumb_t *as, uint fun_id, uint reg_temp); // convenience @@ -480,6 +475,29 @@ void asm_thumb_b_rel12(asm_thumb_t *as, int rel); #define ASM_STORE16_REG_REG(as, reg_src, reg_base) asm_thumb_strh_rlo_rlo_i5((as), (reg_src), (reg_base), 0) #define ASM_STORE32_REG_REG(as, reg_src, reg_base) asm_thumb_str_rlo_rlo_i5((as), (reg_src), (reg_base), 0) +#define ASM_LOAD8_REG_REG_REG(as, reg_dest, reg_base, reg_index) asm_thumb_ldrb_rlo_rlo_rlo((as), (reg_dest), (reg_base), (reg_index)) +#define ASM_LOAD16_REG_REG_REG(as, reg_dest, reg_base, reg_index) \ + do { \ + asm_thumb_lsl_rlo_rlo_i5((as), (reg_index), (reg_index), 1); \ + asm_thumb_ldrh_rlo_rlo_rlo((as), (reg_dest), (reg_base), (reg_index)); \ + } while (0) +#define ASM_LOAD32_REG_REG_REG(as, reg_dest, reg_base, reg_index) \ + do { \ + asm_thumb_lsl_rlo_rlo_i5((as), (reg_index), (reg_index), 2); \ + asm_thumb_ldr_rlo_rlo_rlo((as), (reg_dest), (reg_base), (reg_index)); \ + } while (0) +#define ASM_STORE8_REG_REG_REG(as, reg_val, reg_base, reg_index) asm_thumb_strb_rlo_rlo_rlo((as), (reg_val), (reg_base), (reg_index)) +#define ASM_STORE16_REG_REG_REG(as, reg_val, reg_base, reg_index) \ + do { \ + asm_thumb_lsl_rlo_rlo_i5((as), (reg_index), (reg_index), 1); \ + asm_thumb_strh_rlo_rlo_rlo((as), (reg_val), (reg_base), (reg_index)); \ + } while (0) +#define ASM_STORE32_REG_REG_REG(as, reg_val, reg_base, reg_index) \ + do { \ + asm_thumb_lsl_rlo_rlo_i5((as), (reg_index), (reg_index), 2); \ + asm_thumb_str_rlo_rlo_rlo((as), (reg_val), (reg_base), (reg_index)); \ + } while (0) + #endif // GENERIC_ASM_API #endif // MICROPY_INCLUDED_PY_ASMTHUMB_H diff --git a/py/asmxtensa.h b/py/asmxtensa.h index d2f37bf828e86..6451c75aa597e 100644 --- a/py/asmxtensa.h +++ b/py/asmxtensa.h @@ -411,12 +411,32 @@ void asm_xtensa_call_ind_win(asm_xtensa_t *as, uint idx); #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_xtensa_op_l8ui((as), (reg_dest), (reg_base), 0) #define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_xtensa_op_l16ui((as), (reg_dest), (reg_base), 0) #define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_xtensa_op_l16ui((as), (reg_dest), (reg_base), (uint16_offset)) +#define ASM_LOAD16_REG_REG_REG(as, reg_dest, reg_base, reg_index) \ + do { \ + asm_xtensa_op_addx2((as), (reg_base), (reg_index), (reg_base)); \ + asm_xtensa_op_l16ui((as), (reg_dest), (reg_base), 0); \ + } while (0) #define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_xtensa_op_l32i_n((as), (reg_dest), (reg_base), 0) +#define ASM_LOAD32_REG_REG_REG(as, reg_dest, reg_base, reg_index) \ + do { \ + asm_xtensa_op_addx4((as), (reg_base), (reg_index), (reg_base)); \ + asm_xtensa_op_l32i_n((as), (reg_dest), (reg_base), 0); \ + } while (0) #define ASM_STORE_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_xtensa_s32i_optimised((as), (reg_dest), (reg_base), (word_offset)) #define ASM_STORE8_REG_REG(as, reg_src, reg_base) asm_xtensa_op_s8i((as), (reg_src), (reg_base), 0) #define ASM_STORE16_REG_REG(as, reg_src, reg_base) asm_xtensa_op_s16i((as), (reg_src), (reg_base), 0) +#define ASM_STORE16_REG_REG_REG(as, reg_val, reg_base, reg_index) \ + do { \ + asm_xtensa_op_addx2((as), (reg_base), (reg_index), (reg_base)); \ + asm_xtensa_op_s16i((as), (reg_val), (reg_base), 0); \ + } while (0) #define ASM_STORE32_REG_REG(as, reg_src, reg_base) asm_xtensa_op_s32i_n((as), (reg_src), (reg_base), 0) +#define ASM_STORE32_REG_REG_REG(as, reg_val, reg_base, reg_index) \ + do { \ + asm_xtensa_op_addx4((as), (reg_base), (reg_index), (reg_base)); \ + asm_xtensa_op_s32i_n((as), (reg_val), (reg_base), 0); \ + } while (0) #endif // GENERIC_ASM_API diff --git a/py/emitnative.c b/py/emitnative.c index 088e1e7ae0e11..c2eb46657de33 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -1638,59 +1638,36 @@ static void emit_native_load_subscr(emit_t *emit) { switch (vtype_base) { case VTYPE_PTR8: { // pointer to 8-bit memory - #if N_ARM - asm_arm_ldrb_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); - break; - #elif N_THUMB - asm_thumb_ldrb_rlo_rlo_rlo(emit->as, REG_RET, REG_ARG_1, reg_index); - break; - #endif - // TODO optimise to use thumb ldrb r1, [r2, r3] + #ifdef ASM_LOAD8_REG_REG_REG + ASM_LOAD8_REG_REG_REG(emit->as, REG_RET, REG_ARG_1, reg_index); + #else ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_LOAD8_REG_REG(emit->as, REG_RET, REG_ARG_1); // store value to (base+index) + #endif break; } case VTYPE_PTR16: { // pointer to 16-bit memory - #if N_ARM - asm_arm_ldrh_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); - break; - #elif N_THUMB - asm_thumb_ldrh_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); - break; - #elif N_XTENSA || N_XTENSAWIN - asm_xtensa_op_addx2(emit->as, REG_ARG_1, reg_index, REG_ARG_1); - asm_xtensa_op_l16ui(emit->as, REG_RET, REG_ARG_1, 0); - break; - #endif + #ifdef ASM_LOAD16_REG_REG_REG + ASM_LOAD16_REG_REG_REG(emit->as, REG_RET, REG_ARG_1, reg_index); + #else ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_LOAD16_REG_REG(emit->as, REG_RET, REG_ARG_1); // load from (base+2*index) + #endif break; } case VTYPE_PTR32: { // pointer to word-size memory - #if N_ARM - asm_arm_ldr_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); - break; - #elif N_THUMB - asm_thumb_ldr_reg_reg_reg(emit->as, REG_RET, REG_ARG_1, reg_index); - break; - #elif N_RV32 - asm_rv32_opcode_slli(emit->as, REG_TEMP2, reg_index, 2); - asm_rv32_opcode_cadd(emit->as, REG_ARG_1, REG_TEMP2); - asm_rv32_opcode_lw(emit->as, REG_RET, REG_ARG_1, 0); - break; - #elif N_XTENSA || N_XTENSAWIN - asm_xtensa_op_addx4(emit->as, REG_ARG_1, reg_index, REG_ARG_1); - asm_xtensa_op_l32i_n(emit->as, REG_RET, REG_ARG_1, 0); - break; - #endif + #ifdef ASM_LOAD32_REG_REG_REG + ASM_LOAD32_REG_REG_REG(emit->as, REG_RET, REG_ARG_1, reg_index); + #else ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_LOAD32_REG_REG(emit->as, REG_RET, REG_ARG_1); // load from (base+4*index) + #endif break; } default: @@ -1949,59 +1926,36 @@ static void emit_native_store_subscr(emit_t *emit) { switch (vtype_base) { case VTYPE_PTR8: { // pointer to 8-bit memory - // TODO optimise to use thumb strb r1, [r2, r3] - #if N_ARM - asm_arm_strb_reg_reg_reg(emit->as, reg_value, REG_ARG_1, reg_index); - break; - #elif N_THUMB - asm_thumb_strb_rlo_rlo_rlo(emit->as, reg_value, REG_ARG_1, reg_index); - break; - #endif + #ifdef ASM_STORE8_REG_REG_REG + ASM_STORE8_REG_REG_REG(emit->as, reg_value, REG_ARG_1, reg_index); + #else ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_STORE8_REG_REG(emit->as, reg_value, REG_ARG_1); // store value to (base+index) + #endif break; } case VTYPE_PTR16: { // pointer to 16-bit memory - #if N_ARM - asm_arm_strh_reg_reg_reg(emit->as, reg_value, REG_ARG_1, reg_index); - break; - #elif N_THUMB - asm_thumb_strh_reg_reg_reg(emit->as, reg_value, REG_ARG_1, reg_index); - break; - #elif N_XTENSA || N_XTENSAWIN - asm_xtensa_op_addx2(emit->as, REG_ARG_1, reg_index, REG_ARG_1); - asm_xtensa_op_s16i(emit->as, reg_value, REG_ARG_1, 0); - break; - #endif + #ifdef ASM_STORE16_REG_REG_REG + ASM_STORE16_REG_REG_REG(emit->as, reg_value, REG_ARG_1, reg_index); + #else ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_STORE16_REG_REG(emit->as, reg_value, REG_ARG_1); // store value to (base+2*index) + #endif break; } case VTYPE_PTR32: { // pointer to 32-bit memory - #if N_ARM - asm_arm_str_reg_reg_reg(emit->as, reg_value, REG_ARG_1, reg_index); - break; - #elif N_THUMB - asm_thumb_str_reg_reg_reg(emit->as, reg_value, REG_ARG_1, reg_index); - break; - #elif N_RV32 - asm_rv32_opcode_slli(emit->as, REG_TEMP2, reg_index, 2); - asm_rv32_opcode_cadd(emit->as, REG_ARG_1, REG_TEMP2); - asm_rv32_opcode_sw(emit->as, reg_value, REG_ARG_1, 0); - break; - #elif N_XTENSA || N_XTENSAWIN - asm_xtensa_op_addx4(emit->as, REG_ARG_1, reg_index, REG_ARG_1); - asm_xtensa_op_s32i_n(emit->as, reg_value, REG_ARG_1, 0); - break; - #endif + #ifdef ASM_STORE32_REG_REG_REG + ASM_STORE32_REG_REG_REG(emit->as, reg_value, REG_ARG_1, reg_index); + #else ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_ADD_REG_REG(emit->as, REG_ARG_1, reg_index); // add index to base ASM_STORE32_REG_REG(emit->as, reg_value, REG_ARG_1); // store value to (base+4*index) + #endif break; } default: From e66a6022e2a3263de6f2fa383d4c7d2c4c1cf359 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 7 May 2025 22:45:56 +0200 Subject: [PATCH 134/154] py/asm: Remove unused generic ASM API opcode definitions. This commit removes the ASM_LOAD_REG_REG and ASM_STORE_REG_REG generic ASM API opcodes from all backends, as they are not used anymore in the native emitter framework. Signed-off-by: Alessandro Gatti --- py/asmarm.h | 2 -- py/asmrv32.h | 2 -- py/asmthumb.h | 2 -- py/asmx64.h | 2 -- py/asmx86.h | 2 -- py/emitndebug.c | 4 ---- 6 files changed, 14 deletions(-) diff --git a/py/asmarm.h b/py/asmarm.h index 77c700ff0b87b..42fa1ea212a8a 100644 --- a/py/asmarm.h +++ b/py/asmarm.h @@ -208,14 +208,12 @@ void asm_arm_bx_reg(asm_arm_t *as, uint reg_src); #define ASM_SUB_REG_REG(as, reg_dest, reg_src) asm_arm_sub_reg_reg_reg((as), (reg_dest), (reg_dest), (reg_src)) #define ASM_MUL_REG_REG(as, reg_dest, reg_src) asm_arm_mul_reg_reg_reg((as), (reg_dest), (reg_dest), (reg_src)) -#define ASM_LOAD_REG_REG(as, reg_dest, reg_base) asm_arm_ldr_reg_reg((as), (reg_dest), (reg_base), 0) #define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_arm_ldr_reg_reg((as), (reg_dest), (reg_base), 4 * (word_offset)) #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_arm_ldrb_reg_reg((as), (reg_dest), (reg_base)) #define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_arm_ldrh_reg_reg((as), (reg_dest), (reg_base)) #define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_arm_ldrh_reg_reg_offset((as), (reg_dest), (reg_base), 2 * (uint16_offset)) #define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_arm_ldr_reg_reg((as), (reg_dest), (reg_base), 0) -#define ASM_STORE_REG_REG(as, reg_value, reg_base) asm_arm_str_reg_reg((as), (reg_value), (reg_base), 0) #define ASM_STORE_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_arm_str_reg_reg((as), (reg_dest), (reg_base), 4 * (word_offset)) #define ASM_STORE8_REG_REG(as, reg_value, reg_base) asm_arm_strb_reg_reg((as), (reg_value), (reg_base)) #define ASM_STORE16_REG_REG(as, reg_value, reg_base) asm_arm_strh_reg_reg((as), (reg_value), (reg_base)) diff --git a/py/asmrv32.h b/py/asmrv32.h index 1cbdc88720884..6453b0a3d4367 100644 --- a/py/asmrv32.h +++ b/py/asmrv32.h @@ -737,7 +737,6 @@ void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t source, mp_ #define ASM_LOAD32_REG_REG(state, rd, rs) ASM_LOAD_REG_REG_OFFSET(state, rd, rs, 0) #define ASM_LOAD8_REG_REG(state, rd, rs) asm_rv32_opcode_lbu(state, rd, rs, 0) #define ASM_LOAD_REG_REG_OFFSET(state, rd, rs, offset) asm_rv32_emit_load_reg_reg_offset(state, rd, rs, offset) -#define ASM_LOAD_REG_REG(state, rd, rs) ASM_LOAD32_REG_REG(state, rd, rs) #define ASM_LSL_REG_REG(state, rd, rs) asm_rv32_opcode_sll(state, rd, rd, rs) #define ASM_LSR_REG_REG(state, rd, rs) asm_rv32_opcode_srl(state, rd, rd, rs) #define ASM_MOV_LOCAL_REG(state, local, rs) asm_rv32_emit_mov_local_reg(state, local, rs) @@ -754,7 +753,6 @@ void asm_rv32_emit_store_reg_reg_offset(asm_rv32_t *state, mp_uint_t source, mp_ #define ASM_STORE32_REG_REG(state, rs1, rs2) ASM_STORE_REG_REG_OFFSET(state, rs1, rs2, 0) #define ASM_STORE8_REG_REG(state, rs1, rs2) asm_rv32_opcode_sb(state, rs1, rs2, 0) #define ASM_STORE_REG_REG_OFFSET(state, rd, rs, offset) asm_rv32_emit_store_reg_reg_offset(state, rd, rs, offset) -#define ASM_STORE_REG_REG(state, rs1, rs2) ASM_STORE32_REG_REG(state, rs1, rs2) #define ASM_SUB_REG_REG(state, rd, rs) asm_rv32_opcode_sub(state, rd, rd, rs) #define ASM_XOR_REG_REG(state, rd, rs) asm_rv32_emit_optimised_xor(state, rd, rs) #define ASM_CLR_REG(state, rd) diff --git a/py/asmthumb.h b/py/asmthumb.h index f62f7dd8d9e73..cc4213503bf31 100644 --- a/py/asmthumb.h +++ b/py/asmthumb.h @@ -462,14 +462,12 @@ void asm_thumb_b_rel12(asm_thumb_t *as, int rel); #define ASM_SUB_REG_REG(as, reg_dest, reg_src) asm_thumb_sub_rlo_rlo_rlo((as), (reg_dest), (reg_dest), (reg_src)) #define ASM_MUL_REG_REG(as, reg_dest, reg_src) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_MUL, (reg_dest), (reg_src)) -#define ASM_LOAD_REG_REG(as, reg_dest, reg_base) asm_thumb_ldr_rlo_rlo_i5((as), (reg_dest), (reg_base), 0) #define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_thumb_ldr_reg_reg_i12_optimised((as), (reg_dest), (reg_base), (word_offset)) #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_thumb_ldrb_rlo_rlo_i5((as), (reg_dest), (reg_base), 0) #define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_thumb_ldrh_rlo_rlo_i5((as), (reg_dest), (reg_base), 0) #define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_thumb_ldrh_reg_reg_i12_optimised((as), (reg_dest), (reg_base), (uint16_offset)) #define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_thumb_ldr_rlo_rlo_i5((as), (reg_dest), (reg_base), 0) -#define ASM_STORE_REG_REG(as, reg_src, reg_base) asm_thumb_str_rlo_rlo_i5((as), (reg_src), (reg_base), 0) #define ASM_STORE_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) asm_thumb_str_rlo_rlo_i5((as), (reg_src), (reg_base), (word_offset)) #define ASM_STORE8_REG_REG(as, reg_src, reg_base) asm_thumb_strb_rlo_rlo_i5((as), (reg_src), (reg_base), 0) #define ASM_STORE16_REG_REG(as, reg_src, reg_base) asm_thumb_strh_rlo_rlo_i5((as), (reg_src), (reg_base), 0) diff --git a/py/asmx64.h b/py/asmx64.h index c63e31797ef54..30c6efd6d05b2 100644 --- a/py/asmx64.h +++ b/py/asmx64.h @@ -205,14 +205,12 @@ void asm_x64_call_ind(asm_x64_t *as, size_t fun_id, int temp_r32); #define ASM_SUB_REG_REG(as, reg_dest, reg_src) asm_x64_sub_r64_r64((as), (reg_dest), (reg_src)) #define ASM_MUL_REG_REG(as, reg_dest, reg_src) asm_x64_mul_r64_r64((as), (reg_dest), (reg_src)) -#define ASM_LOAD_REG_REG(as, reg_dest, reg_base) asm_x64_mov_mem64_to_r64((as), (reg_base), 0, (reg_dest)) #define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_x64_mov_mem64_to_r64((as), (reg_base), 8 * (word_offset), (reg_dest)) #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_x64_mov_mem8_to_r64zx((as), (reg_base), 0, (reg_dest)) #define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_x64_mov_mem16_to_r64zx((as), (reg_base), 0, (reg_dest)) #define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_x64_mov_mem16_to_r64zx((as), (reg_base), 2 * (uint16_offset), (reg_dest)) #define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_x64_mov_mem32_to_r64zx((as), (reg_base), 0, (reg_dest)) -#define ASM_STORE_REG_REG(as, reg_src, reg_base) asm_x64_mov_r64_to_mem64((as), (reg_src), (reg_base), 0) #define ASM_STORE_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) asm_x64_mov_r64_to_mem64((as), (reg_src), (reg_base), 8 * (word_offset)) #define ASM_STORE8_REG_REG(as, reg_src, reg_base) asm_x64_mov_r8_to_mem8((as), (reg_src), (reg_base), 0) #define ASM_STORE16_REG_REG(as, reg_src, reg_base) asm_x64_mov_r16_to_mem16((as), (reg_src), (reg_base), 0) diff --git a/py/asmx86.h b/py/asmx86.h index 027d44151e82e..af73c163b4f03 100644 --- a/py/asmx86.h +++ b/py/asmx86.h @@ -200,14 +200,12 @@ void asm_x86_call_ind(asm_x86_t *as, size_t fun_id, mp_uint_t n_args, int temp_r #define ASM_SUB_REG_REG(as, reg_dest, reg_src) asm_x86_sub_r32_r32((as), (reg_dest), (reg_src)) #define ASM_MUL_REG_REG(as, reg_dest, reg_src) asm_x86_mul_r32_r32((as), (reg_dest), (reg_src)) -#define ASM_LOAD_REG_REG(as, reg_dest, reg_base) asm_x86_mov_mem32_to_r32((as), (reg_base), 0, (reg_dest)) #define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) asm_x86_mov_mem32_to_r32((as), (reg_base), 4 * (word_offset), (reg_dest)) #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) asm_x86_mov_mem8_to_r32zx((as), (reg_base), 0, (reg_dest)) #define ASM_LOAD16_REG_REG(as, reg_dest, reg_base) asm_x86_mov_mem16_to_r32zx((as), (reg_base), 0, (reg_dest)) #define ASM_LOAD16_REG_REG_OFFSET(as, reg_dest, reg_base, uint16_offset) asm_x86_mov_mem16_to_r32zx((as), (reg_base), 2 * (uint16_offset), (reg_dest)) #define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) asm_x86_mov_mem32_to_r32((as), (reg_base), 0, (reg_dest)) -#define ASM_STORE_REG_REG(as, reg_src, reg_base) asm_x86_mov_r32_to_mem32((as), (reg_src), (reg_base), 0) #define ASM_STORE_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) asm_x86_mov_r32_to_mem32((as), (reg_src), (reg_base), 4 * (word_offset)) #define ASM_STORE8_REG_REG(as, reg_src, reg_base) asm_x86_mov_r8_to_mem8((as), (reg_src), (reg_base), 0) #define ASM_STORE16_REG_REG(as, reg_src, reg_base) asm_x86_mov_r16_to_mem16((as), (reg_src), (reg_base), 0) diff --git a/py/emitndebug.c b/py/emitndebug.c index bd896a75c8ddf..c068a9a9a126e 100644 --- a/py/emitndebug.c +++ b/py/emitndebug.c @@ -251,8 +251,6 @@ static void asm_debug_setcc_reg_reg_reg(asm_debug_t *as, int op, int reg1, int r #define ASM_MUL_REG_REG(as, reg_dest, reg_src) \ asm_debug_reg_reg(as, "mul", reg_dest, reg_src) -#define ASM_LOAD_REG_REG(as, reg_dest, reg_base) \ - asm_debug_reg_reg(as, "load", reg_dest, reg_base) #define ASM_LOAD_REG_REG_OFFSET(as, reg_dest, reg_base, word_offset) \ asm_debug_reg_reg_offset(as, "load", reg_dest, reg_base, word_offset) #define ASM_LOAD8_REG_REG(as, reg_dest, reg_base) \ @@ -264,8 +262,6 @@ static void asm_debug_setcc_reg_reg_reg(asm_debug_t *as, int op, int reg1, int r #define ASM_LOAD32_REG_REG(as, reg_dest, reg_base) \ asm_debug_reg_reg(as, "load32", reg_dest, reg_base) -#define ASM_STORE_REG_REG(as, reg_src, reg_base) \ - asm_debug_reg_reg(as, "store", reg_src, reg_base) #define ASM_STORE_REG_REG_OFFSET(as, reg_src, reg_base, word_offset) \ asm_debug_reg_reg_offset(as, "store", reg_src, reg_base, word_offset) #define ASM_STORE8_REG_REG(as, reg_src, reg_base) \ From 2260fe0828a87b511ad69c0314ecdcb36b66a2cf Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Wed, 7 May 2025 20:31:38 +0200 Subject: [PATCH 135/154] tests/micropython/viper_ptr: Add tests for arch edge cases. This commit adds a series of test cases to exercise the Viper code generator load/store emitting capabilities on certain boundary conditions. The new test cases check whether the emitted load/store code performs correctly when dealing with specific memory offsets, which trigger specific code generation sequences on different architectures. Right now the cases are for unsigned offsets whose bitmasks span up to 5, 8, and 12 bits (respectively Arm/Thumb, Xtensa, RV32). Signed-off-by: Alessandro Gatti --- .../micropython/viper_ptr16_load_boundary.py | 25 +++++++++++ .../viper_ptr16_load_boundary.py.exp | 12 ++++++ .../micropython/viper_ptr16_store_boundary.py | 41 +++++++++++++++++++ .../viper_ptr16_store_boundary.py.exp | 18 ++++++++ .../micropython/viper_ptr32_load_boundary.py | 25 +++++++++++ .../viper_ptr32_load_boundary.py.exp | 12 ++++++ .../micropython/viper_ptr32_store_boundary.py | 35 ++++++++++++++++ .../viper_ptr32_store_boundary.py.exp | 18 ++++++++ tests/micropython/viper_ptr8_load_boundary.py | 25 +++++++++++ .../viper_ptr8_load_boundary.py.exp | 12 ++++++ .../micropython/viper_ptr8_store_boundary.py | 30 ++++++++++++++ .../viper_ptr8_store_boundary.py.exp | 12 ++++++ 12 files changed, 265 insertions(+) create mode 100644 tests/micropython/viper_ptr16_load_boundary.py create mode 100644 tests/micropython/viper_ptr16_load_boundary.py.exp create mode 100644 tests/micropython/viper_ptr16_store_boundary.py create mode 100644 tests/micropython/viper_ptr16_store_boundary.py.exp create mode 100644 tests/micropython/viper_ptr32_load_boundary.py create mode 100644 tests/micropython/viper_ptr32_load_boundary.py.exp create mode 100644 tests/micropython/viper_ptr32_store_boundary.py create mode 100644 tests/micropython/viper_ptr32_store_boundary.py.exp create mode 100644 tests/micropython/viper_ptr8_load_boundary.py create mode 100644 tests/micropython/viper_ptr8_load_boundary.py.exp create mode 100644 tests/micropython/viper_ptr8_store_boundary.py create mode 100644 tests/micropython/viper_ptr8_store_boundary.py.exp diff --git a/tests/micropython/viper_ptr16_load_boundary.py b/tests/micropython/viper_ptr16_load_boundary.py new file mode 100644 index 0000000000000..ccaaa0909af9d --- /dev/null +++ b/tests/micropython/viper_ptr16_load_boundary.py @@ -0,0 +1,25 @@ +# Test boundary conditions for various architectures + +GET_TEMPLATE = """ +@micropython.viper +def get{off}(src: ptr16) -> int: + return src[{off}] +print(b[{off} * 2:({off} + 1) * 2]) +""" + + +@micropython.viper +def get_index(src: ptr16, i: int) -> int: + return src[i] + + +b = bytearray(5000) +b[28:38] = b"0123456789" +b[252:262] = b"ABCDEFGHIJ" +b[4092:4102] = b"KLMNOPQRST" + +for pre, idx, post in (15, 16, 17), (127, 128, 129), (2047, 2048, 2049): + print(get_index(b, pre), get_index(b, idx), get_index(b, post)) + exec(GET_TEMPLATE.format(off=pre)) + exec(GET_TEMPLATE.format(off=idx)) + exec(GET_TEMPLATE.format(off=post)) diff --git a/tests/micropython/viper_ptr16_load_boundary.py.exp b/tests/micropython/viper_ptr16_load_boundary.py.exp new file mode 100644 index 0000000000000..4b8c184c134cb --- /dev/null +++ b/tests/micropython/viper_ptr16_load_boundary.py.exp @@ -0,0 +1,12 @@ +13106 13620 14134 +bytearray(b'23') +bytearray(b'45') +bytearray(b'67') +17475 17989 18503 +bytearray(b'CD') +bytearray(b'EF') +bytearray(b'GH') +20045 20559 21073 +bytearray(b'MN') +bytearray(b'OP') +bytearray(b'QR') diff --git a/tests/micropython/viper_ptr16_store_boundary.py b/tests/micropython/viper_ptr16_store_boundary.py new file mode 100644 index 0000000000000..e0a4f84557366 --- /dev/null +++ b/tests/micropython/viper_ptr16_store_boundary.py @@ -0,0 +1,41 @@ +# Test boundary conditions for various architectures + +SET_TEMPLATE = """ +@micropython.viper +def set{off}(dest: ptr16): + dest[{off}] = {val} +set{off}(b) +print(b[{off} * 2:({off} + 1) * 2]) +""" + +TEST_DATA = ( + (15, (0x4241, 0x4443, 0x4645)), + (127, (0x4847, 0x4A49, 0x4C4B)), + (2047, (0x4E4D, 0x504F, 0x5251)), +) + + +@micropython.viper +def set_index(dest: ptr16, i: int, val: int): + dest[i] = val + + +@micropython.viper +def set_index(dest: ptr16, i: int, val: int): + dest[i] = val + + +b = bytearray(5000) +for start, vals in TEST_DATA: + for i, v in enumerate(vals): + set_index(b, start + i, v) + print(b[(start + i) * 2 : (start + i + 1) * 2]) + + +for i in range(len(b)): + b[i] = 0 + + +for start, vals in TEST_DATA: + for i, v in enumerate(vals): + exec(SET_TEMPLATE.format(off=start + i, val=v + 0x0101)) diff --git a/tests/micropython/viper_ptr16_store_boundary.py.exp b/tests/micropython/viper_ptr16_store_boundary.py.exp new file mode 100644 index 0000000000000..b56fe6695f298 --- /dev/null +++ b/tests/micropython/viper_ptr16_store_boundary.py.exp @@ -0,0 +1,18 @@ +bytearray(b'AB') +bytearray(b'CD') +bytearray(b'EF') +bytearray(b'GH') +bytearray(b'IJ') +bytearray(b'KL') +bytearray(b'MN') +bytearray(b'OP') +bytearray(b'QR') +bytearray(b'BC') +bytearray(b'DE') +bytearray(b'FG') +bytearray(b'HI') +bytearray(b'JK') +bytearray(b'LM') +bytearray(b'NO') +bytearray(b'PQ') +bytearray(b'RS') diff --git a/tests/micropython/viper_ptr32_load_boundary.py b/tests/micropython/viper_ptr32_load_boundary.py new file mode 100644 index 0000000000000..6954bd46b239f --- /dev/null +++ b/tests/micropython/viper_ptr32_load_boundary.py @@ -0,0 +1,25 @@ +# Test boundary conditions for various architectures + +GET_TEMPLATE = """ +@micropython.viper +def get{off}(src: ptr32) -> int: + return src[{off}] +print(b[{off} * 4:({off} + 1) * 4]) +""" + + +@micropython.viper +def get_index(src: ptr32, i: int) -> int: + return src[i] + + +b = bytearray(5000) +b[24:43] = b"0123456789ABCDEFGHIJ" +b[248:268] = b"KLMNOPQRSTUVWXYZabcd" +b[4088:4108] = b"efghijklmnopqrstuvwx" + +for pre, idx, post in (7, 8, 9), (63, 64, 65), (1023, 1024, 1025): + print(get_index(b, pre), get_index(b, idx), get_index(b, post)) + exec(GET_TEMPLATE.format(off=pre)) + exec(GET_TEMPLATE.format(off=idx)) + exec(GET_TEMPLATE.format(off=post)) diff --git a/tests/micropython/viper_ptr32_load_boundary.py.exp b/tests/micropython/viper_ptr32_load_boundary.py.exp new file mode 100644 index 0000000000000..a58e703f91286 --- /dev/null +++ b/tests/micropython/viper_ptr32_load_boundary.py.exp @@ -0,0 +1,12 @@ +926299444 1111570744 1178944579 +bytearray(b'4567') +bytearray(b'89AB') +bytearray(b'CDEF') +1381060687 1448432723 1515804759 +bytearray(b'OPQR') +bytearray(b'STUV') +bytearray(b'WXYZ') +1818978921 1886350957 1953722993 +bytearray(b'ijkl') +bytearray(b'mnop') +bytearray(b'qrst') diff --git a/tests/micropython/viper_ptr32_store_boundary.py b/tests/micropython/viper_ptr32_store_boundary.py new file mode 100644 index 0000000000000..243ff5cd9c2af --- /dev/null +++ b/tests/micropython/viper_ptr32_store_boundary.py @@ -0,0 +1,35 @@ +# Test boundary conditions for various architectures + +TEST_DATA = ( + (3, (0x04030201, 0x08070605, 0x0C0B0A09)), + (63, (0x100F0E0D, 0x14131211, 0x18171615)), + (1023, (0x1C1B1A19, 0x201F1E1D, 0x24232221)), +) + +SET_TEMPLATE = """ +@micropython.viper +def set{off}(dest: ptr32): + dest[{off}] = {val} & 0x3FFFFFFF +set{off}(b) +print(b[{off} * 4:({off} + 1) * 4]) +""" + + +@micropython.viper +def set_index(dest: ptr32, i: int, val: int): + dest[i] = val + + +b = bytearray(5000) +for start, vals in TEST_DATA: + for i, v in enumerate(vals): + set_index(b, start + i, v) + print(b[(start + i) * 4 : (start + i + 1) * 4]) + +for i in range(len(b)): + b[i] = 0 + + +for start, vals in TEST_DATA: + for i, v in enumerate(vals): + exec(SET_TEMPLATE.format(off=start + i, val=v + 0x01010101)) diff --git a/tests/micropython/viper_ptr32_store_boundary.py.exp b/tests/micropython/viper_ptr32_store_boundary.py.exp new file mode 100644 index 0000000000000..89f09fbc7af5f --- /dev/null +++ b/tests/micropython/viper_ptr32_store_boundary.py.exp @@ -0,0 +1,18 @@ +bytearray(b'\x01\x02\x03\x04') +bytearray(b'\x05\x06\x07\x08') +bytearray(b'\t\n\x0b\x0c') +bytearray(b'\r\x0e\x0f\x10') +bytearray(b'\x11\x12\x13\x14') +bytearray(b'\x15\x16\x17\x18') +bytearray(b'\x19\x1a\x1b\x1c') +bytearray(b'\x1d\x1e\x1f ') +bytearray(b'!"#$') +bytearray(b'\x02\x03\x04\x05') +bytearray(b'\x06\x07\x08\t') +bytearray(b'\n\x0b\x0c\r') +bytearray(b'\x0e\x0f\x10\x11') +bytearray(b'\x12\x13\x14\x15') +bytearray(b'\x16\x17\x18\x19') +bytearray(b'\x1a\x1b\x1c\x1d') +bytearray(b'\x1e\x1f !') +bytearray(b'"#$%') diff --git a/tests/micropython/viper_ptr8_load_boundary.py b/tests/micropython/viper_ptr8_load_boundary.py new file mode 100644 index 0000000000000..bcb17a1e1f2bc --- /dev/null +++ b/tests/micropython/viper_ptr8_load_boundary.py @@ -0,0 +1,25 @@ +# Test boundary conditions for various architectures + +GET_TEMPLATE = """ +@micropython.viper +def get{off}(src: ptr8) -> int: + return src[{off}] +print(get{off}(b)) +""" + + +@micropython.viper +def get_index(src: ptr8, i: int) -> int: + return src[i] + + +b = bytearray(5000) +b[30:32] = b"123" +b[254:256] = b"456" +b[4094:4096] = b"789" + +for pre, idx, post in (30, 31, 32), (254, 255, 256), (4094, 4095, 4096): + print(get_index(b, pre), get_index(b, idx), get_index(b, post)) + exec(GET_TEMPLATE.format(off=pre)) + exec(GET_TEMPLATE.format(off=idx)) + exec(GET_TEMPLATE.format(off=post)) diff --git a/tests/micropython/viper_ptr8_load_boundary.py.exp b/tests/micropython/viper_ptr8_load_boundary.py.exp new file mode 100644 index 0000000000000..7cbd1ac78cff1 --- /dev/null +++ b/tests/micropython/viper_ptr8_load_boundary.py.exp @@ -0,0 +1,12 @@ +49 50 51 +49 +50 +51 +52 53 54 +52 +53 +54 +55 56 57 +55 +56 +57 diff --git a/tests/micropython/viper_ptr8_store_boundary.py b/tests/micropython/viper_ptr8_store_boundary.py new file mode 100644 index 0000000000000..ad512684549a7 --- /dev/null +++ b/tests/micropython/viper_ptr8_store_boundary.py @@ -0,0 +1,30 @@ +# Test boundary conditions for various architectures + +TEST_DATA = ((49, 30, 3), (52, 254, 3), (55, 4094, 3)) + +SET_TEMPLATE = """ +@micropython.viper +def set{off}(dest: ptr8): + dest[{off}] = {val} +set{off}(b) +print(b[{off}]) +""" + + +@micropython.viper +def set_index(dest: ptr8, i: int, val: int): + dest[i] = val + + +b = bytearray(5000) +for val, start, count in TEST_DATA: + for i in range(count): + set_index(b, start + i, val + i) + print(b[start : start + count]) + +for i in range(len(b)): + b[i] = 0 + +for val, start, count in TEST_DATA: + for i in range(count): + exec(SET_TEMPLATE.format(off=start + i, val=val + i + 16)) diff --git a/tests/micropython/viper_ptr8_store_boundary.py.exp b/tests/micropython/viper_ptr8_store_boundary.py.exp new file mode 100644 index 0000000000000..a35cb3ac9e0dd --- /dev/null +++ b/tests/micropython/viper_ptr8_store_boundary.py.exp @@ -0,0 +1,12 @@ +bytearray(b'123') +bytearray(b'456') +bytearray(b'789') +65 +66 +67 +68 +69 +70 +71 +72 +73 From 6b2792a097a841bf1c0a27e4fcffcaacc4968285 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Sat, 19 Apr 2025 22:36:13 +0200 Subject: [PATCH 136/154] py/asmthumb: Generate proper sequences for large register offsets. This commit lets the Thumb native emitter generate a proper opcode sequence when calculating an indexed register offset for load/store operations with said offset beight both greater than 65535 and not able to be represented as a shifted 8-bit bitmask. The original code would assume the scaled index would always fit in 16 bits and silently discard upper bits of the offset. Now an optimised constant loading sequence is emitted instead, and the final offset is also stored in the correct register in all cases. Signed-off-by: Alessandro Gatti --- py/asmthumb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/py/asmthumb.c b/py/asmthumb.c index 420815e80269a..06021f2bc93cb 100644 --- a/py/asmthumb.c +++ b/py/asmthumb.c @@ -450,12 +450,12 @@ static void asm_thumb_add_reg_reg_offset(asm_thumb_t *as, uint reg_dest, uint re asm_thumb_lsl_rlo_rlo_i5(as, reg_dest, reg_dest, offset_shift); asm_thumb_add_rlo_rlo_rlo(as, reg_dest, reg_dest, reg_base); } else if (reg_dest != reg_base) { - asm_thumb_mov_rlo_i16(as, reg_dest, offset << offset_shift); - asm_thumb_add_rlo_rlo_rlo(as, reg_dest, reg_dest, reg_dest); + asm_thumb_mov_reg_i32_optimised(as, reg_dest, offset << offset_shift); + asm_thumb_add_rlo_rlo_rlo(as, reg_dest, reg_dest, reg_base); } else { uint reg_other = reg_dest ^ 7; asm_thumb_op16(as, OP_PUSH_RLIST((1 << reg_other))); - asm_thumb_mov_rlo_i16(as, reg_other, offset << offset_shift); + asm_thumb_mov_reg_i32_optimised(as, reg_other, offset << offset_shift); asm_thumb_add_rlo_rlo_rlo(as, reg_dest, reg_dest, reg_other); asm_thumb_op16(as, OP_POP_RLIST((1 << reg_other))); } From 3d19a8bc2df1d153aedde587e8f8e9fded4d6c08 Mon Sep 17 00:00:00 2001 From: Alessandro Gatti Date: Thu, 8 May 2025 00:02:26 +0200 Subject: [PATCH 137/154] py/emitnative: Clean up int-indexed Viper load/store code. This commit performs some minor clean up for the code involved in Viper load/store operations when said operations have an integer index. Most platform-specific code blocks were able to generate correct opcodes even when the index is 0, but they would still fall back to the general case. The general case would still emit a shortened opcode sequence so this commit does not alter the overall behaviour, but makes it easier to extend platform-specific code whenever the full index range is going to be handled rather than a subset of indices as it is now. Signed-off-by: Alessandro Gatti --- py/emitnative.c | 198 ++++++++++++++++++++++++------------------------ 1 file changed, 99 insertions(+), 99 deletions(-) diff --git a/py/emitnative.c b/py/emitnative.c index c2eb46657de33..4b470f3c93522 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -1537,25 +1537,24 @@ static void emit_native_load_subscr(emit_t *emit) { switch (vtype_base) { case VTYPE_PTR8: { // pointer to 8-bit memory - // TODO optimise to use thumb ldrb r1, [r2, r3] + #if N_THUMB + if (index_value >= 0 && index_value < 32) { + asm_thumb_ldrb_rlo_rlo_i5(emit->as, REG_RET, reg_base, index_value); + break; + } + #elif N_RV32 + if (FIT_SIGNED(index_value, 12)) { + asm_rv32_opcode_lbu(emit->as, REG_RET, reg_base, index_value); + break; + } + #elif N_XTENSA || N_XTENSAWIN + if (index_value >= 0 && index_value < 256) { + asm_xtensa_op_l8ui(emit->as, REG_RET, reg_base, index_value); + break; + } + #endif if (index_value != 0) { // index is non-zero - #if N_THUMB - if (index_value > 0 && index_value < 32) { - asm_thumb_ldrb_rlo_rlo_i5(emit->as, REG_RET, reg_base, index_value); - break; - } - #elif N_RV32 - if (FIT_SIGNED(index_value, 12)) { - asm_rv32_opcode_lbu(emit->as, REG_RET, reg_base, index_value); - break; - } - #elif N_XTENSA || N_XTENSAWIN - if (index_value > 0 && index_value < 256) { - asm_xtensa_op_l8ui(emit->as, REG_RET, reg_base, index_value); - break; - } - #endif need_reg_single(emit, reg_index, 0); ASM_MOV_REG_IMM(emit->as, reg_index, index_value); ASM_ADD_REG_REG(emit->as, reg_index, reg_base); // add index to base @@ -1566,24 +1565,24 @@ static void emit_native_load_subscr(emit_t *emit) { } case VTYPE_PTR16: { // pointer to 16-bit memory + #if N_THUMB + if (index_value >= 0 && index_value < 32) { + asm_thumb_ldrh_rlo_rlo_i5(emit->as, REG_RET, reg_base, index_value); + break; + } + #elif N_RV32 + if (FIT_SIGNED(index_value, 11)) { + asm_rv32_opcode_lhu(emit->as, REG_RET, reg_base, index_value << 1); + break; + } + #elif N_XTENSA || N_XTENSAWIN + if (index_value >= 0 && index_value < 256) { + asm_xtensa_op_l16ui(emit->as, REG_RET, reg_base, index_value); + break; + } + #endif if (index_value != 0) { // index is a non-zero immediate - #if N_THUMB - if (index_value > 0 && index_value < 32) { - asm_thumb_ldrh_rlo_rlo_i5(emit->as, REG_RET, reg_base, index_value); - break; - } - #elif N_RV32 - if (FIT_SIGNED(index_value, 11)) { - asm_rv32_opcode_lhu(emit->as, REG_RET, reg_base, index_value << 1); - break; - } - #elif N_XTENSA || N_XTENSAWIN - if (index_value > 0 && index_value < 256) { - asm_xtensa_op_l16ui(emit->as, REG_RET, reg_base, index_value); - break; - } - #endif need_reg_single(emit, reg_index, 0); ASM_MOV_REG_IMM(emit->as, reg_index, index_value << 1); ASM_ADD_REG_REG(emit->as, reg_index, reg_base); // add 2*index to base @@ -1594,24 +1593,24 @@ static void emit_native_load_subscr(emit_t *emit) { } case VTYPE_PTR32: { // pointer to 32-bit memory + #if N_THUMB + if (index_value >= 0 && index_value < 32) { + asm_thumb_ldr_rlo_rlo_i5(emit->as, REG_RET, reg_base, index_value); + break; + } + #elif N_RV32 + if (FIT_SIGNED(index_value, 10)) { + asm_rv32_opcode_lw(emit->as, REG_RET, reg_base, index_value << 2); + break; + } + #elif N_XTENSA || N_XTENSAWIN + if (index_value >= 0 && index_value < 256) { + asm_xtensa_l32i_optimised(emit->as, REG_RET, reg_base, index_value); + break; + } + #endif if (index_value != 0) { // index is a non-zero immediate - #if N_THUMB - if (index_value > 0 && index_value < 32) { - asm_thumb_ldr_rlo_rlo_i5(emit->as, REG_RET, reg_base, index_value); - break; - } - #elif N_RV32 - if (FIT_SIGNED(index_value, 10)) { - asm_rv32_opcode_lw(emit->as, REG_RET, reg_base, index_value << 2); - break; - } - #elif N_XTENSA || N_XTENSAWIN - if (index_value > 0 && index_value < 256) { - asm_xtensa_l32i_optimised(emit->as, REG_RET, reg_base, index_value); - break; - } - #endif need_reg_single(emit, reg_index, 0); ASM_MOV_REG_IMM(emit->as, reg_index, index_value << 2); ASM_ADD_REG_REG(emit->as, reg_index, reg_base); // add 4*index to base @@ -1811,28 +1810,28 @@ static void emit_native_store_subscr(emit_t *emit) { case VTYPE_PTR8: { // pointer to 8-bit memory // TODO optimise to use thumb strb r1, [r2, r3] + #if N_THUMB + if (index_value >= 0 && index_value < 32) { + asm_thumb_strb_rlo_rlo_i5(emit->as, reg_value, reg_base, index_value); + break; + } + #elif N_RV32 + if (FIT_SIGNED(index_value, 12)) { + asm_rv32_opcode_sb(emit->as, reg_value, reg_base, index_value); + break; + } + #elif N_XTENSA || N_XTENSAWIN + if (index_value >= 0 && index_value < 256) { + asm_xtensa_op_s8i(emit->as, reg_value, reg_base, index_value); + break; + } + #endif if (index_value != 0) { // index is non-zero - #if N_THUMB - if (index_value > 0 && index_value < 32) { - asm_thumb_strb_rlo_rlo_i5(emit->as, reg_value, reg_base, index_value); - break; - } - #elif N_RV32 - if (FIT_SIGNED(index_value, 12)) { - asm_rv32_opcode_sb(emit->as, reg_value, reg_base, index_value); - break; - } - #elif N_XTENSA || N_XTENSAWIN - if (index_value > 0 && index_value < 256) { - asm_xtensa_op_s8i(emit->as, REG_RET, reg_base, index_value); - break; - } - #endif ASM_MOV_REG_IMM(emit->as, reg_index, index_value); #if N_ARM asm_arm_strb_reg_reg_reg(emit->as, reg_value, reg_base, reg_index); - return; + break; #endif ASM_ADD_REG_REG(emit->as, reg_index, reg_base); // add index to base reg_base = reg_index; @@ -1842,24 +1841,24 @@ static void emit_native_store_subscr(emit_t *emit) { } case VTYPE_PTR16: { // pointer to 16-bit memory + #if N_THUMB + if (index_value >= 0 && index_value < 32) { + asm_thumb_strh_rlo_rlo_i5(emit->as, reg_value, reg_base, index_value); + break; + } + #elif N_RV32 + if (FIT_SIGNED(index_value, 11)) { + asm_rv32_opcode_sh(emit->as, reg_value, reg_base, index_value << 1); + break; + } + #elif N_XTENSA || N_XTENSAWIN + if (index_value >= 0 && index_value < 256) { + asm_xtensa_op_s16i(emit->as, reg_value, reg_base, index_value); + break; + } + #endif if (index_value != 0) { // index is a non-zero immediate - #if N_THUMB - if (index_value > 0 && index_value < 32) { - asm_thumb_strh_rlo_rlo_i5(emit->as, reg_value, reg_base, index_value); - break; - } - #elif N_RV32 - if (FIT_SIGNED(index_value, 11)) { - asm_rv32_opcode_sh(emit->as, reg_value, reg_base, index_value << 1); - break; - } - #elif N_XTENSA || N_XTENSAWIN - if (index_value > 0 && index_value < 256) { - asm_xtensa_op_s16i(emit->as, REG_RET, reg_base, index_value); - break; - } - #endif ASM_MOV_REG_IMM(emit->as, reg_index, index_value << 1); ASM_ADD_REG_REG(emit->as, reg_index, reg_base); // add 2*index to base reg_base = reg_index; @@ -1869,27 +1868,28 @@ static void emit_native_store_subscr(emit_t *emit) { } case VTYPE_PTR32: { // pointer to 32-bit memory + #if N_THUMB + if (index_value >= 0 && index_value < 32) { + asm_thumb_str_rlo_rlo_i5(emit->as, reg_value, reg_base, index_value); + break; + } + #elif N_RV32 + if (FIT_SIGNED(index_value, 10)) { + asm_rv32_opcode_sw(emit->as, reg_value, reg_base, index_value << 2); + break; + } + #elif N_XTENSA || N_XTENSAWIN + if (index_value >= 0 && index_value < 256) { + asm_xtensa_s32i_optimised(emit->as, reg_value, reg_base, index_value); + break; + } + #endif if (index_value != 0) { // index is a non-zero immediate - #if N_THUMB - if (index_value > 0 && index_value < 32) { - asm_thumb_str_rlo_rlo_i5(emit->as, reg_value, reg_base, index_value); - break; - } - #elif N_RV32 - if (FIT_SIGNED(index_value, 10)) { - asm_rv32_opcode_sw(emit->as, reg_value, reg_base, index_value << 2); - break; - } - #elif N_XTENSA || N_XTENSAWIN - if (index_value > 0 && index_value < 256) { - asm_xtensa_s32i_optimised(emit->as, REG_RET, reg_base, index_value); - break; - } - #elif N_ARM + #if N_ARM ASM_MOV_REG_IMM(emit->as, reg_index, index_value); asm_arm_str_reg_reg_reg(emit->as, reg_value, reg_base, reg_index); - return; + break; #endif ASM_MOV_REG_IMM(emit->as, reg_index, index_value << 2); ASM_ADD_REG_REG(emit->as, reg_index, reg_base); // add 4*index to base From 3d7edbd9ab80182b0a11a5df3860dd12f65597a4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 22 Apr 2025 11:48:30 +1000 Subject: [PATCH 138/154] py/persistentcode: Allow a port a custom commit function and track data. Allows both MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA and MP_PLAT_COMMIT_EXEC to be enabled at the same time. Signed-off-by: Damien George --- py/persistentcode.c | 14 ++++++++------ py/runtime.c | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/py/persistentcode.c b/py/persistentcode.c index 2a42b904bc138..43207a0cc8f66 100644 --- a/py/persistentcode.c +++ b/py/persistentcode.c @@ -72,6 +72,8 @@ typedef struct _bytecode_prelude_t { static int read_byte(mp_reader_t *reader); static size_t read_uint(mp_reader_t *reader); +#if MICROPY_EMIT_MACHINE_CODE + #if MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA || MICROPY_PERSISTENT_CODE_TRACK_BSS_RODATA // An mp_obj_list_t that tracks native text/BSS/rodata to prevent the GC from reclaiming them. @@ -86,8 +88,6 @@ static void track_root_pointer(void *ptr) { #endif -#if MICROPY_EMIT_MACHINE_CODE - typedef struct _reloc_info_t { mp_reader_t *reader; mp_module_context_t *context; @@ -415,15 +415,17 @@ static mp_raw_code_t *load_raw_code(mp_reader_t *reader, mp_module_context_t *co // Relocate and commit code to executable address space reloc_info_t ri = {reader, context, rodata, bss}; + #if MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA + if (native_scope_flags & MP_SCOPE_FLAG_VIPERRELOC) { + // Track the function data memory so it's not reclaimed by the GC. + track_root_pointer(fun_data); + } + #endif #if defined(MP_PLAT_COMMIT_EXEC) void *opt_ri = (native_scope_flags & MP_SCOPE_FLAG_VIPERRELOC) ? &ri : NULL; fun_data = MP_PLAT_COMMIT_EXEC(fun_data, fun_data_len, opt_ri); #else if (native_scope_flags & MP_SCOPE_FLAG_VIPERRELOC) { - #if MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA - // Track the function data memory so it's not reclaimed by the GC. - track_root_pointer(fun_data); - #endif // Do the relocations. mp_native_relocate(&ri, fun_data, (uintptr_t)fun_data); } diff --git a/py/runtime.c b/py/runtime.c index d6fea172f212b..7979e520da481 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -123,7 +123,7 @@ void mp_init(void) { MP_STATE_VM(mp_module_builtins_override_dict) = NULL; #endif - #if MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA || MICROPY_PERSISTENT_CODE_TRACK_BSS_RODATA + #if MICROPY_EMIT_MACHINE_CODE && (MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA || MICROPY_PERSISTENT_CODE_TRACK_BSS_RODATA) MP_STATE_VM(persistent_code_root_pointers) = MP_OBJ_NULL; #endif From d5db8f04613b6a7e6f3d55dd8d618e8ccdaecdfa Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 22 Apr 2025 11:50:34 +1000 Subject: [PATCH 139/154] nrf: Use correct IRAM address for native code execution on nRF52. On nRF52, the physical SRAM is mapped to 0x20000000 for data access and 0x00800000 for instruction access. So, while native code is allocated and written using addresses in the 0x20000000 range, it must execute from the 0x00800000 range. This commit makes this work correctly on nRF52 MCUs by adjusting the address. Signed-off-by: Damien George --- ports/nrf/main.c | 14 ++++++++++++++ ports/nrf/mpconfigport.h | 12 +++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/ports/nrf/main.c b/ports/nrf/main.c index 99ed39895eb5a..784a031ce29bb 100644 --- a/ports/nrf/main.c +++ b/ports/nrf/main.c @@ -39,6 +39,7 @@ #include "py/stackctrl.h" #include "py/gc.h" #include "py/compile.h" +#include "py/persistentcode.h" #include "extmod/modmachine.h" #include "shared/runtime/pyexec.h" #include "readline.h" @@ -369,3 +370,16 @@ void MP_WEAK __assert_func(const char *file, int line, const char *func, const c printf("Assertion '%s' failed, at file %s:%d\n", expr, file, line); __fatal_error("Assertion failed"); } + +#if MICROPY_EMIT_MACHINE_CODE +void *nrf_native_code_commit(void *buf, unsigned int len, void *reloc) { + (void)len; + if (reloc) { + // Native code in RAM must execute from the IRAM region at 0x00800000, and so relocations + // to text must also point to this region. The MICROPY_MAKE_POINTER_CALLABLE macro will + // adjust the `buf` address from RAM to IRAM. + mp_native_relocate(reloc, buf, (uintptr_t)MICROPY_MAKE_POINTER_CALLABLE(buf) & ~1); + } + return buf; +} +#endif diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index 7cc8a66d9840c..8c5c0300628dc 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -320,7 +320,17 @@ // type definitions for the specific machine -#define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) +#if defined(NRF52832) || defined(NRF52840) +// On nRF52, the physical SRAM is mapped to 0x20000000 for data access and 0x00800000 +// for instruction access. So convert addresses to make them executable. +#define MICROPY_PERSISTENT_CODE_TRACK_FUN_DATA (1) +#define MICROPY_PERSISTENT_CODE_TRACK_BSS_RODATA (0) +#define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)(((uintptr_t)(p) - 0x20000000 + 0x00800000) | 1)) +void *nrf_native_code_commit(void *, unsigned int, void *); +#define MP_PLAT_COMMIT_EXEC(buf, len, reloc) nrf_native_code_commit(buf, len, reloc) +#else +#define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((uintptr_t)(p) | 1)) +#endif #define MP_SSIZE_MAX (0x7fffffff) From 32c65ad4552d7e7d88c06e94fd903dcea5080c5f Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 22 Apr 2025 11:51:35 +1000 Subject: [PATCH 140/154] nrf: Only process interrupt chars on UARTs used for REPL. This commit adds an `attached_to_repl` property to each UART, and makes sure that property is correctly set/unset when the UART is attached to or detached from the REPL. That property is then used to make sure incoming characters on the UART are only checked for the interrupt character if the UART is attached to the REPL. Otherwise a board without REPL on UART can have its code interrupted if ctrl-C is received on the UART. Also, put incoming UART characters on to `stdin_ringbuf` instead of the UARTs ring buffer (the former is much larger than the latter). Signed-off-by: Damien George --- ports/nrf/main.c | 4 +++- ports/nrf/modules/machine/uart.c | 26 ++++++++++++++++++-------- ports/nrf/modules/machine/uart.h | 3 +-- ports/nrf/modules/os/modos.c | 14 +++++++++++--- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/ports/nrf/main.c b/ports/nrf/main.c index 784a031ce29bb..21a71c7c9e48c 100644 --- a/ports/nrf/main.c +++ b/ports/nrf/main.c @@ -40,6 +40,7 @@ #include "py/gc.h" #include "py/compile.h" #include "py/persistentcode.h" +#include "extmod/misc.h" #include "extmod/modmachine.h" #include "shared/runtime/pyexec.h" #include "readline.h" @@ -172,7 +173,8 @@ void MP_NORETURN _start(void) { MP_OBJ_NEW_SMALL_INT(MICROPY_HW_UART_REPL), MP_OBJ_NEW_SMALL_INT(MICROPY_HW_UART_REPL_BAUD), }; - MP_STATE_VM(dupterm_objs[0]) = MP_OBJ_TYPE_GET_SLOT(&machine_uart_type, make_new)((mp_obj_t)&machine_uart_type, MP_ARRAY_SIZE(args), 0, args); + mp_obj_t uart = MP_OBJ_TYPE_GET_SLOT(&machine_uart_type, make_new)((mp_obj_t)&machine_uart_type, MP_ARRAY_SIZE(args), 0, args); + mp_os_dupterm_obj.fun.var(1, &uart); } #endif diff --git a/ports/nrf/modules/machine/uart.c b/ports/nrf/modules/machine/uart.c index 8d5a73e095f5e..45c79cda12a69 100644 --- a/ports/nrf/modules/machine/uart.c +++ b/ports/nrf/modules/machine/uart.c @@ -104,6 +104,7 @@ typedef struct _machine_uart_obj_t { uint16_t timeout_char; // timeout waiting between chars (in ms) uint8_t uart_id; bool initialized; // static flag. Initialized to False + bool attached_to_repl; #if MICROPY_PY_MACHINE_UART_IRQ uint16_t mp_irq_trigger; // user IRQ trigger mask uint16_t mp_irq_flags; // user IRQ active IRQ flags @@ -118,6 +119,13 @@ static machine_uart_obj_t machine_uart_obj[] = { }; void uart_init0(void) { + for (int i = 0; i < MP_ARRAY_SIZE(machine_uart_obj); i++) { + machine_uart_obj[i].attached_to_repl = false; + } +} + +void uart_attach_to_repl(machine_uart_obj_t *self, bool attached) { + self->attached_to_repl = attached; } static int uart_find(mp_obj_t id) { @@ -137,14 +145,16 @@ static void uart_event_handler(nrfx_uart_event_t const *p_event, void *p_context if (p_event->type == NRFX_UART_EVT_RX_DONE) { nrfx_uart_rx(self->p_uart, &self->buf.rx_buf[0], 1); int chr = self->buf.rx_buf[0]; - #if !MICROPY_PY_BLE_NUS && MICROPY_KBD_EXCEPTION - if (chr == mp_interrupt_char) { - self->buf.rx_ringbuf.iget = 0; - self->buf.rx_ringbuf.iput = 0; - mp_sched_keyboard_interrupt(); - } else - #endif - { + if (self->attached_to_repl) { + #if MICROPY_KBD_EXCEPTION + if (chr == mp_interrupt_char) { + mp_sched_keyboard_interrupt(); + } else + #endif + { + ringbuf_put((ringbuf_t *)&stdin_ringbuf, chr); + } + } else { ringbuf_put((ringbuf_t *)&self->buf.rx_ringbuf, chr); } #if MICROPY_PY_MACHINE_UART_IRQ diff --git a/ports/nrf/modules/machine/uart.h b/ports/nrf/modules/machine/uart.h index 741473ab7a4e3..85c2092458541 100644 --- a/ports/nrf/modules/machine/uart.h +++ b/ports/nrf/modules/machine/uart.h @@ -34,8 +34,7 @@ typedef struct _machine_uart_obj_t machine_uart_obj_t; void uart_init0(void); -void uart_deinit(void); -void uart_irq_handler(mp_uint_t uart_id); +void uart_attach_to_repl(machine_uart_obj_t *self, bool attached); bool uart_rx_any(machine_uart_obj_t *uart_obj); int uart_rx_char(machine_uart_obj_t *uart_obj); diff --git a/ports/nrf/modules/os/modos.c b/ports/nrf/modules/os/modos.c index f000e1eeb6457..97ed1e1badbbc 100644 --- a/ports/nrf/modules/os/modos.c +++ b/ports/nrf/modules/os/modos.c @@ -30,6 +30,7 @@ #include "py/runtime.h" #include "extmod/modmachine.h" #include "drivers/rng.h" +#include "modules/machine/uart.h" #if MICROPY_PY_OS_URANDOM // Return a bytes object with n random bytes, generated by the hardware random number generator. @@ -46,10 +47,17 @@ static MP_DEFINE_CONST_FUN_OBJ_1(mp_os_urandom_obj, mp_os_urandom); #endif #if MICROPY_PY_OS_DUPTERM -// TODO should accept any object with read/write methods. void mp_os_dupterm_stream_detached_attached(mp_obj_t stream_detached, mp_obj_t stream_attached) { - if (!(stream_attached == mp_const_none || mp_obj_get_type(stream_attached) == &machine_uart_type)) { - mp_raise_ValueError(MP_ERROR_TEXT("need a UART object")); + #if MICROPY_PY_MACHINE_UART + if (mp_obj_get_type(stream_detached) == &machine_uart_type) { + uart_attach_to_repl(MP_OBJ_TO_PTR(stream_detached), false); } + #endif + + #if MICROPY_PY_MACHINE_UART + if (mp_obj_get_type(stream_attached) == &machine_uart_type) { + uart_attach_to_repl(MP_OBJ_TO_PTR(stream_attached), true); + } + #endif } #endif // MICROPY_PY_OS_DUPTERM From e676b58d9fae4c4ebebfe0ea7fb787317dafba38 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 22 Apr 2025 11:52:35 +1000 Subject: [PATCH 141/154] nrf: Fix UART write on parts that can't write more than 255 bytes. Some MCUs cannot write more than 255 bytes to the UART at once. Eg writing 256 bytes gets truncated to 0, writing 257 gets truncated to 1, etc. Signed-off-by: Damien George --- ports/nrf/modules/machine/uart.c | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/ports/nrf/modules/machine/uart.c b/ports/nrf/modules/machine/uart.c index 45c79cda12a69..4c75bf8209830 100644 --- a/ports/nrf/modules/machine/uart.c +++ b/ports/nrf/modules/machine/uart.c @@ -46,6 +46,11 @@ #include "nrfx_uarte.h" #endif +#if defined(NRF52832) +// The nRF52832 cannot write more than 255 bytes at a time. +#define UART_MAX_TX_CHUNK (255) +#endif + typedef struct _machine_uart_buf_t { uint8_t tx_buf[1]; uint8_t rx_buf[1]; @@ -456,17 +461,29 @@ static mp_uint_t mp_machine_uart_write(mp_obj_t self_in, const void *buf, mp_uin #endif machine_uart_obj_t *self = self_in; - nrfx_err_t err = nrfx_uart_tx(self->p_uart, buf, size); - if (err == NRFX_SUCCESS) { + + // Send data out, in chunks if needed. + mp_uint_t remaining = size; + while (remaining) { + #ifdef UART_MAX_TX_CHUNK + mp_uint_t chunk = MIN(UART_MAX_TX_CHUNK, remaining); + #else + mp_uint_t chunk = remaining; + #endif + nrfx_err_t err = nrfx_uart_tx(self->p_uart, buf, chunk); + if (err != NRFX_SUCCESS) { + *errcode = mp_hal_status_to_errno_table[err]; + return MP_STREAM_ERROR; + } while (nrfx_uart_tx_in_progress(self->p_uart)) { MICROPY_EVENT_POLL_HOOK; } - // return number of bytes written - return size; - } else { - *errcode = mp_hal_status_to_errno_table[err]; - return MP_STREAM_ERROR; + buf += chunk; + remaining -= chunk; } + + // return number of bytes written + return size; } static mp_uint_t mp_machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { From cc7eb1a5351c0d7d60f084dc79ccd0fa047b259e Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 22 Apr 2025 11:54:13 +1000 Subject: [PATCH 142/154] nrf/boards: Use 64 byte raw-paste buffer on PCA10028 and PCA10040. To workaround issues with JLink CDC. Signed-off-by: Damien George --- ports/nrf/boards/PCA10028/mpconfigboard.h | 5 +++++ ports/nrf/boards/PCA10040/mpconfigboard.h | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/ports/nrf/boards/PCA10028/mpconfigboard.h b/ports/nrf/boards/PCA10028/mpconfigboard.h index 3dc8ed3459b53..df2e4e85d931f 100644 --- a/ports/nrf/boards/PCA10028/mpconfigboard.h +++ b/ports/nrf/boards/PCA10028/mpconfigboard.h @@ -60,3 +60,8 @@ #define MICROPY_HW_SPI0_MISO (28) #define HELP_TEXT_BOARD_LED "1,2,3,4" + +// The JLink CDC on the PCA10028 cannot accept more than 64 incoming bytes at a time. +// That makes the UART REPL unreliable in general. But it can be improved to some +// extent by setting the raw-paste buffer size to that limit of 64. +#define MICROPY_REPL_STDIN_BUFFER_MAX (64) diff --git a/ports/nrf/boards/PCA10040/mpconfigboard.h b/ports/nrf/boards/PCA10040/mpconfigboard.h index b965bf319d082..2b1c4c7ef5cfb 100644 --- a/ports/nrf/boards/PCA10040/mpconfigboard.h +++ b/ports/nrf/boards/PCA10040/mpconfigboard.h @@ -64,3 +64,8 @@ #define MICROPY_HW_PWM2_NAME "PWM2" #define HELP_TEXT_BOARD_LED "1,2,3,4" + +// The JLink CDC on the PCA10040 cannot accept more than 64 incoming bytes at a time. +// That makes the UART REPL unreliable in general. But it can be improved to some +// extent by setting the raw-paste buffer size to that limit of 64. +#define MICROPY_REPL_STDIN_BUFFER_MAX (64) From a1ee42cd3e6172cd57756e382a4702ae2a1d4a9e Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 22 Apr 2025 11:53:18 +1000 Subject: [PATCH 143/154] nrf: Use common implementation of machine disable/enable IRQ. This is a breaking change due to the signature change of `enable_irq()`. Previously the signature was: machine.enable_irq() Now the signature matches other ports, and the docs, and is: machine.enable_irq(state) Where `state` is the return value from `machine.disable_irq()`. Signed-off-by: Damien George --- ports/nrf/modules/machine/modmachine.c | 23 ----------------------- ports/nrf/mpconfigport.h | 1 + ports/nrf/mphalport.h | 16 ++++++++++++++++ 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/ports/nrf/modules/machine/modmachine.c b/ports/nrf/modules/machine/modmachine.c index aa1f5a8a4a0c4..f543265479c48 100644 --- a/ports/nrf/modules/machine/modmachine.c +++ b/ports/nrf/modules/machine/modmachine.c @@ -85,8 +85,6 @@ #define MICROPY_PY_MACHINE_EXTRA_GLOBALS \ { MP_ROM_QSTR(MP_QSTR_info), MP_ROM_PTR(&machine_info_obj) }, \ - { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&machine_enable_irq_obj) }, \ - { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&machine_disable_irq_obj) }, \ { MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&machine_lightsleep_obj) }, \ { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&pin_type) }, \ \ @@ -214,24 +212,3 @@ static mp_obj_t mp_machine_get_freq(void) { static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { mp_raise_NotImplementedError(NULL); } - -static mp_obj_t machine_enable_irq(void) { - #ifndef BLUETOOTH_SD - __enable_irq(); - #else - - #endif - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_0(machine_enable_irq_obj, machine_enable_irq); - -// Resets the board in a manner similar to pushing the external RESET button. -static mp_obj_t machine_disable_irq(void) { - #ifndef BLUETOOTH_SD - __disable_irq(); - #else - - #endif - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_0(machine_disable_irq_obj, machine_disable_irq); diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index 8c5c0300628dc..d52b5745d4e8f 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -181,6 +181,7 @@ #define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_BOOTLOADER (1) +#define MICROPY_PY_MACHINE_DISABLE_IRQ_ENABLE_IRQ (1) #define MICROPY_PY_MACHINE_PULSE (0) #define MICROPY_PY_MACHINE_SOFTI2C (MICROPY_PY_MACHINE_I2C) diff --git a/ports/nrf/mphalport.h b/ports/nrf/mphalport.h index 3a89636aec6bc..12e881d7b636b 100644 --- a/ports/nrf/mphalport.h +++ b/ports/nrf/mphalport.h @@ -35,6 +35,22 @@ #include "nrfx_config.h" #include "shared/runtime/interrupt_char.h" +// Entering a critical section. +#ifndef BLUETOOTH_SD +#define MICROPY_BEGIN_ATOMIC_SECTION() disable_irq() +#define MICROPY_END_ATOMIC_SECTION(state) enable_irq(state) +#endif + +static inline void enable_irq(mp_uint_t state) { + __set_PRIMASK(state); +} + +static inline mp_uint_t disable_irq(void) { + mp_uint_t state = __get_PRIMASK(); + __disable_irq(); + return state; +} + typedef enum { HAL_OK = 0x00, From 4545eb844df3acf342052bf80b35a8127cf7e194 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 26 Mar 2025 11:21:09 +1100 Subject: [PATCH 144/154] rp2: Disable the LWIP tick timer when not needed. Prevents lightsleep being woken up every 64ms to service LWIP timers, when: 1. No netif is up, and 2. No TCP sockets are active The TCP socket check may not be strictly necessary, but without ticking the tcp timer they won't ever time out by themselves. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/rp2/mpnetworkport.c | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/ports/rp2/mpnetworkport.c b/ports/rp2/mpnetworkport.c index e1e1567828bfb..fed34be380a50 100644 --- a/ports/rp2/mpnetworkport.c +++ b/ports/rp2/mpnetworkport.c @@ -31,6 +31,7 @@ #if MICROPY_PY_LWIP #include "shared/runtime/softtimer.h" +#include "lwip/netif.h" #include "lwip/timeouts.h" // Poll lwIP every 64ms by default @@ -39,6 +40,9 @@ // Soft timer for running lwIP in the background. static soft_timer_entry_t mp_network_soft_timer; +// Callback for change of netif state +NETIF_DECLARE_EXT_CALLBACK(netif_callback) + #if MICROPY_PY_NETWORK_CYW43 #include "lib/cyw43-driver/src/cyw43.h" #include "lib/cyw43-driver/src/cyw43_stats.h" @@ -137,17 +141,48 @@ static void mp_network_soft_timer_callback(soft_timer_entry_t *self) { #if MICROPY_PY_NETWORK_WIZNET5K wiznet5k_poll(); #endif + + // Only keep the timer running if any TCP sockets are active, or any netif is up + struct netif *netif; + extern void *tcp_active_pcbs; + bool keep_running = (tcp_active_pcbs != NULL); + if (!keep_running) { + NETIF_FOREACH(netif) { + if (netif->flags & NETIF_FLAG_LINK_UP) { + keep_running = true; + break; + } + } + } + + // Periodic timer will re-queue as soon as this handler exits, + // one shot timer will not + mp_network_soft_timer.mode = keep_running ? SOFT_TIMER_MODE_PERIODIC : SOFT_TIMER_MODE_ONE_SHOT; } +static void mp_network_netif_status_cb(struct netif *netif, netif_nsc_reason_t reason, const netif_ext_callback_args_t *args); + void mod_network_lwip_init(void) { soft_timer_static_init( &mp_network_soft_timer, - SOFT_TIMER_MODE_PERIODIC, + SOFT_TIMER_MODE_ONE_SHOT, LWIP_TICK_RATE_MS, mp_network_soft_timer_callback ); - soft_timer_reinsert(&mp_network_soft_timer, LWIP_TICK_RATE_MS); + if (netif_callback.callback_fn == NULL) { + netif_add_ext_callback(&netif_callback, mp_network_netif_status_cb); + } +} + +static void mp_network_netif_status_cb(struct netif *netif, netif_nsc_reason_t reason, const netif_ext_callback_args_t *args) { + // Start the network soft timer any time an interface comes up, unless + // it's already running + if (reason == LWIP_NSC_LINK_CHANGED && args->link_changed.state + && mp_network_soft_timer.mode == SOFT_TIMER_MODE_ONE_SHOT) { + mp_network_soft_timer.mode = SOFT_TIMER_MODE_PERIODIC; + soft_timer_reinsert(&mp_network_soft_timer, LWIP_TICK_RATE_MS); + } } #endif // MICROPY_PY_LWIP From 28c8fff6d8f920eb6199cc5db59da1baf1b00843 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 12 May 2025 10:21:52 +0100 Subject: [PATCH 145/154] rp2/machine_pin: Replace macros with Pico SDK functions. Replace custom macros with Pico SDK functions, enabling support for RP2350B variant chips with > 32 GPIOs. Fixes issue #17241. Signed-off-by: Phil Howard --- ports/rp2/machine_pin.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/ports/rp2/machine_pin.c b/ports/rp2/machine_pin.c index d9ca3f8ce37a5..db3ca69d8b1ae 100644 --- a/ports/rp2/machine_pin.c +++ b/ports/rp2/machine_pin.c @@ -46,12 +46,6 @@ #define GPIO_IRQ_ALL (0xf) -// Macros to access the state of the hardware. -#define GPIO_GET_FUNCSEL(id) ((iobank0_hw->io[(id)].ctrl & IO_BANK0_GPIO0_CTRL_FUNCSEL_BITS) >> IO_BANK0_GPIO0_CTRL_FUNCSEL_LSB) -#define GPIO_IS_OUT(id) (sio_hw->gpio_oe & (1 << (id))) -#define GPIO_IS_PULL_UP(id) (padsbank0_hw->io[(id)] & PADS_BANK0_GPIO0_PUE_BITS) -#define GPIO_IS_PULL_DOWN(id) (padsbank0_hw->io[(id)] & PADS_BANK0_GPIO0_PDE_BITS) - // Open drain behaviour is simulated. #define GPIO_IS_OPEN_DRAIN(id) (machine_pin_open_drain_mask & (1 << (id))) @@ -198,13 +192,13 @@ const machine_pin_obj_t *machine_pin_find(mp_obj_t pin) { static void machine_pin_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_pin_obj_t *self = self_in; - uint funcsel = GPIO_GET_FUNCSEL(self->id); + uint funcsel = gpio_get_function(self->id); qstr mode_qst; if (!is_ext_pin(self)) { if (funcsel == GPIO_FUNC_SIO) { if (GPIO_IS_OPEN_DRAIN(self->id)) { mode_qst = MP_QSTR_OPEN_DRAIN; - } else if (GPIO_IS_OUT(self->id)) { + } else if (gpio_is_dir_out(self->id)) { mode_qst = MP_QSTR_OUT; } else { mode_qst = MP_QSTR_IN; @@ -214,11 +208,11 @@ static void machine_pin_print(const mp_print_t *print, mp_obj_t self_in, mp_prin } mp_printf(print, "Pin(%q, mode=%q", self->name, mode_qst); bool pull_up = false; - if (GPIO_IS_PULL_UP(self->id)) { + if (gpio_is_pulled_up(self->id)) { mp_printf(print, ", pull=%q", MP_QSTR_PULL_UP); pull_up = true; } - if (GPIO_IS_PULL_DOWN(self->id)) { + if (gpio_is_pulled_down(self->id)) { if (pull_up) { mp_printf(print, "|%q", MP_QSTR_PULL_DOWN); } else { @@ -411,7 +405,7 @@ static mp_obj_t machine_pin_toggle(mp_obj_t self_in) { machine_pin_ext_set(self, self->last_output_value ^ 1); #endif } else if (GPIO_IS_OPEN_DRAIN(self->id)) { - if (GPIO_IS_OUT(self->id)) { + if (gpio_is_dir_out(self->id)) { gpio_set_dir(self->id, GPIO_IN); } else { gpio_set_dir(self->id, GPIO_OUT); From 45cb9b4444a7316a137f9c32942bc6c76f904f01 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 12 May 2025 11:08:54 +0100 Subject: [PATCH 146/154] rp2/machine_pin: Fix simulated open drain with more than 32 GPIOs. Changes are: - Refactor the open-drain macros, add GPIO_ENABLE/DISABLE_OPEN_DRAIN, and move them to `mphalport.h`. - Only use `uint64_t` for the open-drain mask if there are more than 32 GPIOs (saves code size). - Ensure we're shifting a `uint64_t` by using 1ULL constants. Signed-off-by: Phil Howard --- ports/rp2/machine_pin.c | 9 +++++---- ports/rp2/mphalport.h | 17 ++++++++++++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/ports/rp2/machine_pin.c b/ports/rp2/machine_pin.c index db3ca69d8b1ae..7a2de0c0bc690 100644 --- a/ports/rp2/machine_pin.c +++ b/ports/rp2/machine_pin.c @@ -46,9 +46,6 @@ #define GPIO_IRQ_ALL (0xf) -// Open drain behaviour is simulated. -#define GPIO_IS_OPEN_DRAIN(id) (machine_pin_open_drain_mask & (1 << (id))) - #ifndef MICROPY_HW_PIN_RESERVED #define MICROPY_HW_PIN_RESERVED(i) (0) #endif @@ -83,7 +80,11 @@ static const mp_irq_methods_t machine_pin_irq_methods; static const int num_intr_regs = sizeof(iobank0_hw->intr) / sizeof(iobank0_hw->intr[0]); // Mask with "1" indicating that the corresponding pin is in simulated open-drain mode. +#if NUM_BANK0_GPIOS > 32 uint64_t machine_pin_open_drain_mask; +#else +uint32_t machine_pin_open_drain_mask; +#endif #if MICROPY_HW_PIN_EXT_COUNT static inline bool is_ext_pin(__unused const machine_pin_obj_t *self) { @@ -292,7 +293,7 @@ static mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_ mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("invalid pin af: %d"), af); } gpio_set_function(self->id, af); - machine_pin_open_drain_mask &= ~(1ULL << self->id); + GPIO_DISABLE_OPEN_DRAIN(self->id); } } diff --git a/ports/rp2/mphalport.h b/ports/rp2/mphalport.h index 956db3ec702fb..5012b682b9d29 100644 --- a/ports/rp2/mphalport.h +++ b/ports/rp2/mphalport.h @@ -123,7 +123,18 @@ static inline mp_uint_t mp_hal_get_cpu_freq(void) { #define MP_HAL_PIN_PULL_UP (1) #define MP_HAL_PIN_PULL_DOWN (2) +// Open drain behaviour is simulated. +#if NUM_BANK0_GPIOS > 32 extern uint64_t machine_pin_open_drain_mask; +#define GPIO_IS_OPEN_DRAIN(id) (machine_pin_open_drain_mask & (1ULL << id)) +#define GPIO_ENABLE_OPEN_DRAIN(id) (machine_pin_open_drain_mask |= (1ULL << id)) +#define GPIO_DISABLE_OPEN_DRAIN(id) (machine_pin_open_drain_mask &= ~(1ULL << id)) +#else +extern uint32_t machine_pin_open_drain_mask; +#define GPIO_IS_OPEN_DRAIN(id) (machine_pin_open_drain_mask & (1U << id)) +#define GPIO_ENABLE_OPEN_DRAIN(id) (machine_pin_open_drain_mask |= (1U << id)) +#define GPIO_DISABLE_OPEN_DRAIN(id) (machine_pin_open_drain_mask &= ~(1U << id)) +#endif mp_hal_pin_obj_t mp_hal_get_pin_obj(mp_obj_t pin_in); @@ -133,13 +144,13 @@ static inline unsigned int mp_hal_pin_name(mp_hal_pin_obj_t pin) { static inline void mp_hal_pin_input(mp_hal_pin_obj_t pin) { gpio_set_dir(pin, GPIO_IN); - machine_pin_open_drain_mask &= ~(1 << pin); + GPIO_DISABLE_OPEN_DRAIN(pin); gpio_set_function(pin, GPIO_FUNC_SIO); } static inline void mp_hal_pin_output(mp_hal_pin_obj_t pin) { gpio_set_dir(pin, GPIO_OUT); - machine_pin_open_drain_mask &= ~(1 << pin); + GPIO_DISABLE_OPEN_DRAIN(pin); gpio_set_function(pin, GPIO_FUNC_SIO); } @@ -151,7 +162,7 @@ static inline void mp_hal_pin_open_drain_with_value(mp_hal_pin_obj_t pin, int v) gpio_put(pin, 0); gpio_set_dir(pin, GPIO_OUT); } - machine_pin_open_drain_mask |= 1 << pin; + GPIO_ENABLE_OPEN_DRAIN(pin); gpio_set_function(pin, GPIO_FUNC_SIO); } From 7f6fedef2aeb0234f3c7d35a735ade75b9fdeaf3 Mon Sep 17 00:00:00 2001 From: Malcolm McKellips Date: Tue, 20 May 2025 13:23:25 -0600 Subject: [PATCH 147/154] rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA: Fix default I2C to use I2C1. Signed-off-by: Damien George --- ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/mpconfigboard.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/mpconfigboard.h b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/mpconfigboard.h index 56071e1873341..931391d972e0c 100644 --- a/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/mpconfigboard.h +++ b/ports/rp2/boards/SPARKFUN_XRP_CONTROLLER_BETA/mpconfigboard.h @@ -24,5 +24,9 @@ int mp_hal_is_pin_reserved(int n); #define MICROPY_HW_PIN_RESERVED(i) mp_hal_is_pin_reserved(i) +// Set the default I2C to I2C1 on pins 18 and 19 which route to the qwiic connector +#undef PICO_DEFAULT_I2C +#define PICO_DEFAULT_I2C (1) + #define MICROPY_HW_I2C1_SDA (18) #define MICROPY_HW_I2C1_SCL (19) From dc1af386a81f0238d809b2443714dc8feba5f174 Mon Sep 17 00:00:00 2001 From: Malcolm McKellips Date: Mon, 17 Mar 2025 09:19:44 -0600 Subject: [PATCH 148/154] esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32: Add SparkFun board. Add board definition files for SparkFun IoT RedBoard ESP32. Signed-off-by: Malcolm McKellips --- .../SPARKFUN_IOT_REDBOARD_ESP32/board.json | 23 +++++++++++++++++++ .../SPARKFUN_IOT_REDBOARD_ESP32/manifest.py | 2 ++ .../mpconfigboard.cmake | 7 ++++++ .../mpconfigboard.h | 14 +++++++++++ .../SPARKFUN_IOT_REDBOARD_ESP32/pins.csv | 22 ++++++++++++++++++ 5 files changed, 68 insertions(+) create mode 100644 ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/board.json create mode 100644 ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/manifest.py create mode 100644 ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/mpconfigboard.cmake create mode 100644 ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/mpconfigboard.h create mode 100644 ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/pins.csv diff --git a/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/board.json b/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/board.json new file mode 100644 index 0000000000000..8bce1a001336f --- /dev/null +++ b/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/board.json @@ -0,0 +1,23 @@ +{ + "deploy": [ + "../deploy.md" + ], + "deploy_options": { + "flash_offset": "0x1000" + }, + "docs": "", + "features": [ + "BLE", + "External Flash", + "WiFi", + "USB-C" + ], + "images": [ + "19177-Sparkfun_IoT_Redboard-ESP32.jpg" + ], + "mcu": "esp32", + "product": "ESP32 / WROOM", + "thumbnail": "", + "url": "https://www.sparkfun.com/sparkfun-iot-redboard-esp32-development-board.html", + "vendor": "SparkFun" +} diff --git a/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/manifest.py b/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/manifest.py new file mode 100644 index 0000000000000..73446ecac9892 --- /dev/null +++ b/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/manifest.py @@ -0,0 +1,2 @@ +include("$(PORT_DIR)/boards/manifest.py") +require("sdcard") diff --git a/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/mpconfigboard.cmake b/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/mpconfigboard.cmake new file mode 100644 index 0000000000000..0ffcc38c877b7 --- /dev/null +++ b/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/mpconfigboard.cmake @@ -0,0 +1,7 @@ +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.ble + boards/sdkconfig.240mhz +) + +set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) diff --git a/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/mpconfigboard.h b/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/mpconfigboard.h new file mode 100644 index 0000000000000..4fe888ac17c9a --- /dev/null +++ b/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/mpconfigboard.h @@ -0,0 +1,14 @@ +// Board and hardware specific configuration + +#define MICROPY_HW_BOARD_NAME "SparkFun IoT RedBoard ESP32" +#define MICROPY_HW_MCU_NAME "ESP32" + +// Enable UART REPL for modules that have an external USB-UART and don't use native USB. +#define MICROPY_HW_ENABLE_UART_REPL (1) + +#define MICROPY_HW_I2C0_SCL (22) +#define MICROPY_HW_I2C0_SDA (21) + +#define MICROPY_HW_SPI1_SCK (18) +#define MICROPY_HW_SPI1_MOSI (23) +#define MICROPY_HW_SPI1_MISO (19) diff --git a/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/pins.csv b/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/pins.csv new file mode 100644 index 0000000000000..d37c51af8f775 --- /dev/null +++ b/ports/esp32/boards/SPARKFUN_IOT_REDBOARD_ESP32/pins.csv @@ -0,0 +1,22 @@ +TX,GPIO1 +RX,GPIO3 +ALERT,GPIO4 +TCK,GPIO13 +TMS,GPIO14 +CS,GPIO5 +PICO,GPIO23 +POCI,GPIO19 +SCK,GPIO18 +SDA,GPIO21 +SCL,GPIO22 +A0,GPIO36 +A3,GPIO39 +A4,GPIO32 +A5,GPIO33 +A6,GPIO34 +A7,GPIO35 +LED,GPIO18 +LED_BLUE,GPIO18 +BLUE_LED,GPIO18 +RGB_LED,GPIO2 +NEOPIXEL,GPIO2 From 49f81d5046aaeb31f90626426363ae2518dbd810 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 20 May 2025 10:34:56 +1000 Subject: [PATCH 149/154] tests/float/math_constants.py: Test actual e and pi constant values. The existing test for `math.e` and `math.pi` constants can fail on certain targets if the functions `math.exp()` and/or `math.cos()` are not accurate enough (eg out by an LSB of float precision). For example this test currently fails on PYBD_SF6 which uses double precision floats (and that's due to the `lib/libm_dbl/exp.c` implementation not being exact). This commit changes this constant test so that it tests the actual constant value, not the evaluation of `exp()` and `cos()` functions. Signed-off-by: Damien George --- tests/float/math_constants.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/tests/float/math_constants.py b/tests/float/math_constants.py index 2e4c321052625..21d822a01e3e8 100644 --- a/tests/float/math_constants.py +++ b/tests/float/math_constants.py @@ -1,11 +1,30 @@ # Tests various constants of the math module. + +import sys + try: - import math - from math import exp, cos + from array import array + from math import e, pi except ImportError: print("SKIP") raise SystemExit -print(math.e == exp(1.0)) +# Hexadecimal representations of e and pi constants. +e_truth_single = 0x402DF854 +pi_truth_single = 0x40490FDB +e_truth_double = 0x4005BF0A8B145769 +pi_truth_double = 0x400921FB54442D18 + +# Detect the floating-point precision of the system, to determine the exact values of +# the constants (parsing the float from a decimal string can lead to inaccuracies). +if float("1e300") == float("inf"): + # Single precision floats. + e_truth = array("f", e_truth_single.to_bytes(4, sys.byteorder))[0] + pi_truth = array("f", pi_truth_single.to_bytes(4, sys.byteorder))[0] +else: + # Double precision floats. + e_truth = array("d", e_truth_double.to_bytes(8, sys.byteorder))[0] + pi_truth = array("d", pi_truth_double.to_bytes(8, sys.byteorder))[0] -print(cos(math.pi)) +print("e:", e == e_truth or (e, e_truth, e - e_truth)) +print("pi:", pi == pi_truth or (pi, pi_truth, pi - pi_truth)) From 22f1d766334f96f48aaaa04a72faeb9a2a37b595 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 22 May 2025 17:38:22 +1000 Subject: [PATCH 150/154] shared/tinyusb: Use device event hook to schedule USB task. Previously MicroPython ports would linker-wrap dcd_event_handler in order to schedule the USB task callback to run when needed. TinyUSB 0.16 added proper support for an event hook to do the same thing without the hacky linker wrapping. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/alif/alif.mk | 4 ---- ports/esp32/esp32_common.cmake | 8 -------- ports/nrf/Makefile | 1 - ports/renesas-ra/Makefile | 3 --- ports/rp2/CMakeLists.txt | 1 - ports/samd/Makefile | 2 -- shared/tinyusb/mp_usbd.c | 13 ++----------- 7 files changed, 2 insertions(+), 30 deletions(-) diff --git a/ports/alif/alif.mk b/ports/alif/alif.mk index eee27b3b7d698..bb07a3aa2025c 100644 --- a/ports/alif/alif.mk +++ b/ports/alif/alif.mk @@ -103,10 +103,6 @@ CFLAGS += -Wl,-T$(BUILD)/ensemble.ld \ -Wl,--print-memory-usage \ -Wl,--no-warn-rwx-segment -ifeq ($(MCU_CORE),M55_HP) -CFLAGS += -Wl,--wrap=dcd_event_handler -endif - ################################################################################ # Source files and libraries diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 2e7b95b385be5..09b120391305f 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -97,10 +97,6 @@ if(MICROPY_PY_TINYUSB) list(APPEND MICROPY_INC_TINYUSB ${MICROPY_DIR}/shared/tinyusb/ ) - - list(APPEND MICROPY_LINK_TINYUSB - -Wl,--wrap=dcd_event_handler - ) endif() list(APPEND MICROPY_SOURCE_PORT @@ -261,10 +257,6 @@ target_compile_options(${MICROPY_TARGET} PUBLIC -Wno-missing-field-initializers ) -target_link_options(${MICROPY_TARGET} PUBLIC - ${MICROPY_LINK_TINYUSB} -) - # Additional include directories needed for private NimBLE headers. target_include_directories(${MICROPY_TARGET} PUBLIC ${IDF_PATH}/components/bt/host/nimble/nimble diff --git a/ports/nrf/Makefile b/ports/nrf/Makefile index 59e74dce4215a..d3d747186ff98 100644 --- a/ports/nrf/Makefile +++ b/ports/nrf/Makefile @@ -268,7 +268,6 @@ SRC_C += $(addprefix lib/tinyusb/src/,\ portable/nordic/nrf5x/dcd_nrf5x.c \ ) -LDFLAGS += -Wl,--wrap=dcd_event_handler endif DRIVERS_SRC_C += $(addprefix modules/,\ diff --git a/ports/renesas-ra/Makefile b/ports/renesas-ra/Makefile index ec47510d981ad..fd74c60a8564e 100644 --- a/ports/renesas-ra/Makefile +++ b/ports/renesas-ra/Makefile @@ -157,9 +157,6 @@ LIBSTDCPP_FILE_NAME = "$(shell $(CXX) $(CXXFLAGS) -print-file-name=libstdc++.a)" LDFLAGS += -L"$(shell dirname $(LIBSTDCPP_FILE_NAME))" endif -# Hook tinyusb USB interrupt if used to service usb task. -LDFLAGS += --wrap=dcd_event_handler - # Options for mpy-cross MPY_CROSS_FLAGS += -march=armv7m diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 7cb0c60aa21f4..f0b278df2bb34 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -525,7 +525,6 @@ target_compile_options(${MICROPY_TARGET} PRIVATE target_link_options(${MICROPY_TARGET} PRIVATE -Wl,--defsym=__micropy_c_heap_size__=${MICROPY_C_HEAP_SIZE} - -Wl,--wrap=dcd_event_handler -Wl,--wrap=runtime_init_clocks ) diff --git a/ports/samd/Makefile b/ports/samd/Makefile index 005664f178d3c..bec530d803f7e 100644 --- a/ports/samd/Makefile +++ b/ports/samd/Makefile @@ -113,8 +113,6 @@ LIBSTDCPP_FILE_NAME = "$(shell $(CXX) $(CXXFLAGS) -print-file-name=libstdc++.a)" LDFLAGS += -L"$(shell dirname $(LIBSTDCPP_FILE_NAME))" endif -LDFLAGS += --wrap=dcd_event_handler - MPY_CROSS_FLAGS += -march=$(MPY_CROSS_MCU_ARCH) SRC_C += \ diff --git a/shared/tinyusb/mp_usbd.c b/shared/tinyusb/mp_usbd.c index 7ccfa7018d4bc..f03f7f7db419a 100644 --- a/shared/tinyusb/mp_usbd.c +++ b/shared/tinyusb/mp_usbd.c @@ -30,10 +30,6 @@ #include "mp_usbd.h" -#ifndef NO_QSTR -#include "device/dcd.h" -#endif - #if !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE void mp_usbd_task(void) { @@ -47,13 +43,8 @@ void mp_usbd_task_callback(mp_sched_node_t *node) { #endif // !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE -extern void __real_dcd_event_handler(dcd_event_t const *event, bool in_isr); - -// If -Wl,--wrap=dcd_event_handler is passed to the linker, then this wrapper -// will be called and allows MicroPython to schedule the TinyUSB task when -// dcd_event_handler() is called from an ISR. -TU_ATTR_FAST_FUNC void __wrap_dcd_event_handler(dcd_event_t const *event, bool in_isr) { - __real_dcd_event_handler(event, in_isr); +// Schedule the TinyUSB task on demand, when there is a new USB device event +TU_ATTR_FAST_FUNC void tud_event_hook_cb(uint8_t rhport, uint32_t eventid, bool in_isr) { mp_usbd_schedule_task(); mp_hal_wake_main_task_from_isr(); } From b36111b12c74889b1a9fdb2bdc63b8953389f30c Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 22 May 2025 17:46:58 +1000 Subject: [PATCH 151/154] nrf: Revert "nrf/Makefile: Enable LTO by default only on newer gcc.". This reverts commit 62e0fa04a7a6f9044db1bb0f20ea7a2e00599921. Reverting as the only linker wrap needed for nrf port was removed in the parent commit. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/nrf/Makefile | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ports/nrf/Makefile b/ports/nrf/Makefile index d3d747186ff98..7b16974f97023 100644 --- a/ports/nrf/Makefile +++ b/ports/nrf/Makefile @@ -129,14 +129,7 @@ CFLAGS_MCU_m4 = $(CFLAGS_CORTEX_M) -mtune=cortex-m4 -mcpu=cortex-m4 -mfpu=fpv4-s CFLAGS_MCU_m0 = $(CFLAGS_CORTEX_M) -fshort-enums -mtune=cortex-m0 -mcpu=cortex-m0 -mfloat-abi=soft -# linker wrap does not work with lto on older gcc/binutils: https://sourceware.org/bugzilla/show_bug.cgi?id=24406 -GCC_VERSION = $(shell arm-none-eabi-gcc -dumpversion) -GCC_MAJOR_VERS = $(word 1,$(subst ., ,$(GCC_VERSION))) -ifeq ($(shell test $(GCC_MAJOR_VERS) -ge 10; echo $$?),0) LTO ?= 1 -else -LTO ?= 0 -endif ifeq ($(LTO),1) CFLAGS += -flto From 670b7c93505a3995bcb7ba9af5cb721994414a1f Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Thu, 24 Oct 2024 15:52:55 +0530 Subject: [PATCH 152/154] zephyr/boards: Add support for BeaglePlay CC1352p7. - Enable support for FLASH and IEEE802154 subg radio - Requires Zephyr v3.8.0 Signed-off-by: Ayush Singh --- ports/zephyr/boards/beagleplay_cc1352p7.conf | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 ports/zephyr/boards/beagleplay_cc1352p7.conf diff --git a/ports/zephyr/boards/beagleplay_cc1352p7.conf b/ports/zephyr/boards/beagleplay_cc1352p7.conf new file mode 100644 index 0000000000000..f63d184e38ef0 --- /dev/null +++ b/ports/zephyr/boards/beagleplay_cc1352p7.conf @@ -0,0 +1,13 @@ +# Flash drivers +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y + +# Networking +CONFIG_BT=n +CONFIG_NET_IPV4=n +CONFIG_NET_CONFIG_NEED_IPV6=y +CONFIG_NET_CONFIG_NEED_IPV4=n +CONFIG_NET_CONFIG_MY_IPV4_ADDR="" +CONFIG_NET_L2_IEEE802154=y +CONFIG_NET_DHCPV4=n +CONFIG_NET_CONFIG_IEEE802154_CHANNEL=1 From 9dbae39348e2c95190d808bc14a0e8adb1c3b8be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Tue, 13 May 2025 13:50:42 +0200 Subject: [PATCH 153/154] lib/littlefs: Update LittleFS to v2.11. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: DaniĆ«l van de Giessen --- lib/littlefs/lfs2.c | 80 +++++++++++++++++++++++++++------------- lib/littlefs/lfs2.h | 8 +++- lib/littlefs/lfs2_util.h | 16 ++++---- 3 files changed, 68 insertions(+), 36 deletions(-) diff --git a/lib/littlefs/lfs2.c b/lib/littlefs/lfs2.c index f9ce2cf290686..abdec19d7cb6d 100644 --- a/lib/littlefs/lfs2.c +++ b/lib/littlefs/lfs2.c @@ -3932,7 +3932,9 @@ static int lfs2_remove_(lfs2_t *lfs2, const char *path) { } lfs2->mlist = dir.next; - if (lfs2_tag_type3(tag) == LFS2_TYPE_DIR) { + if (lfs2_gstate_hasorphans(&lfs2->gstate)) { + LFS2_ASSERT(lfs2_tag_type3(tag) == LFS2_TYPE_DIR); + // fix orphan err = lfs2_fs_preporphans(lfs2, -1); if (err) { @@ -4076,8 +4078,10 @@ static int lfs2_rename_(lfs2_t *lfs2, const char *oldpath, const char *newpath) } lfs2->mlist = prevdir.next; - if (prevtag != LFS2_ERR_NOENT - && lfs2_tag_type3(prevtag) == LFS2_TYPE_DIR) { + if (lfs2_gstate_hasorphans(&lfs2->gstate)) { + LFS2_ASSERT(prevtag != LFS2_ERR_NOENT + && lfs2_tag_type3(prevtag) == LFS2_TYPE_DIR); + // fix orphan err = lfs2_fs_preporphans(lfs2, -1); if (err) { @@ -5233,40 +5237,64 @@ static int lfs2_fs_gc_(lfs2_t *lfs2) { #endif #ifndef LFS2_READONLY +#ifdef LFS2_SHRINKNONRELOCATING +static int lfs2_shrink_checkblock(void *data, lfs2_block_t block) { + lfs2_size_t threshold = *((lfs2_size_t*)data); + if (block >= threshold) { + return LFS2_ERR_NOTEMPTY; + } + return 0; +} +#endif + static int lfs2_fs_grow_(lfs2_t *lfs2, lfs2_size_t block_count) { - // shrinking is not supported - LFS2_ASSERT(block_count >= lfs2->block_count); + int err; - if (block_count > lfs2->block_count) { - lfs2->block_count = block_count; + if (block_count == lfs2->block_count) { + return 0; + } - // fetch the root - lfs2_mdir_t root; - int err = lfs2_dir_fetch(lfs2, &root, lfs2->root); + +#ifndef LFS2_SHRINKNONRELOCATING + // shrinking is not supported + LFS2_ASSERT(block_count >= lfs2->block_count); +#endif +#ifdef LFS2_SHRINKNONRELOCATING + if (block_count < lfs2->block_count) { + err = lfs2_fs_traverse_(lfs2, lfs2_shrink_checkblock, &block_count, true); if (err) { return err; } + } +#endif - // update the superblock - lfs2_superblock_t superblock; - lfs2_stag_t tag = lfs2_dir_get(lfs2, &root, LFS2_MKTAG(0x7ff, 0x3ff, 0), - LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)), - &superblock); - if (tag < 0) { - return tag; - } - lfs2_superblock_fromle32(&superblock); + lfs2->block_count = block_count; - superblock.block_count = lfs2->block_count; + // fetch the root + lfs2_mdir_t root; + err = lfs2_dir_fetch(lfs2, &root, lfs2->root); + if (err) { + return err; + } - lfs2_superblock_tole32(&superblock); - err = lfs2_dir_commit(lfs2, &root, LFS2_MKATTRS( - {tag, &superblock})); - if (err) { - return err; - } + // update the superblock + lfs2_superblock_t superblock; + lfs2_stag_t tag = lfs2_dir_get(lfs2, &root, LFS2_MKTAG(0x7ff, 0x3ff, 0), + LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock); + if (tag < 0) { + return tag; } + lfs2_superblock_fromle32(&superblock); + + superblock.block_count = lfs2->block_count; + lfs2_superblock_tole32(&superblock); + err = lfs2_dir_commit(lfs2, &root, LFS2_MKATTRS( + {tag, &superblock})); + if (err) { + return err; + } return 0; } #endif diff --git a/lib/littlefs/lfs2.h b/lib/littlefs/lfs2.h index f503fd00bcfa2..77477aaff83c0 100644 --- a/lib/littlefs/lfs2.h +++ b/lib/littlefs/lfs2.h @@ -21,7 +21,7 @@ extern "C" // Software library version // Major (top-nibble), incremented on backwards incompatible changes // Minor (bottom-nibble), incremented on feature additions -#define LFS2_VERSION 0x0002000a +#define LFS2_VERSION 0x0002000b #define LFS2_VERSION_MAJOR (0xffff & (LFS2_VERSION >> 16)) #define LFS2_VERSION_MINOR (0xffff & (LFS2_VERSION >> 0)) @@ -766,7 +766,11 @@ int lfs2_fs_gc(lfs2_t *lfs2); // Grows the filesystem to a new size, updating the superblock with the new // block count. // -// Note: This is irreversible. +// If LFS2_SHRINKNONRELOCATING is defined, this function will also accept +// block_counts smaller than the current configuration, after checking +// that none of the blocks that are being removed are in use. +// Note that littlefs's pseudorandom block allocation means that +// this is very unlikely to work in the general case. // // Returns a negative error code on failure. int lfs2_fs_grow(lfs2_t *lfs2, lfs2_size_t block_count); diff --git a/lib/littlefs/lfs2_util.h b/lib/littlefs/lfs2_util.h index 3b191f6885f44..12c82a630b079 100644 --- a/lib/littlefs/lfs2_util.h +++ b/lib/littlefs/lfs2_util.h @@ -195,10 +195,10 @@ static inline uint32_t lfs2_fromle32(uint32_t a) { (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) return __builtin_bswap32(a); #else - return (((uint8_t*)&a)[0] << 0) | - (((uint8_t*)&a)[1] << 8) | - (((uint8_t*)&a)[2] << 16) | - (((uint8_t*)&a)[3] << 24); + return ((uint32_t)((uint8_t*)&a)[0] << 0) | + ((uint32_t)((uint8_t*)&a)[1] << 8) | + ((uint32_t)((uint8_t*)&a)[2] << 16) | + ((uint32_t)((uint8_t*)&a)[3] << 24); #endif } @@ -218,10 +218,10 @@ static inline uint32_t lfs2_frombe32(uint32_t a) { (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) return a; #else - return (((uint8_t*)&a)[0] << 24) | - (((uint8_t*)&a)[1] << 16) | - (((uint8_t*)&a)[2] << 8) | - (((uint8_t*)&a)[3] << 0); + return ((uint32_t)((uint8_t*)&a)[0] << 24) | + ((uint32_t)((uint8_t*)&a)[1] << 16) | + ((uint32_t)((uint8_t*)&a)[2] << 8) | + ((uint32_t)((uint8_t*)&a)[3] << 0); #endif } From 2dada065ac4333d8623fd2c7509c47d15d8c9a5e Mon Sep 17 00:00:00 2001 From: Herwin Grobben Date: Thu, 22 May 2025 12:26:59 +0200 Subject: [PATCH 154/154] stm32: Allow QSPI to work on STM32G4. Adding a QSPI memory chip on a STM32G4 does not work due to some small issues, which are fixed in this commit: - Rename QUADSPI1_xxx alt-func names to QUADSPI_xxx, to match the static names used in `qspi.c`. - Enable `mpu.h` macros on G4. - Don't include I- and D-cache invalidation on G4. Signed-off-by: Damien George --- ports/stm32/boards/stm32g474_af.csv | 58 ++++++++++++++--------------- ports/stm32/mpu.h | 2 +- ports/stm32/qspi.c | 2 + 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/ports/stm32/boards/stm32g474_af.csv b/ports/stm32/boards/stm32g474_af.csv index 5853f5f9008af..59a0e91a14f85 100644 --- a/ports/stm32/boards/stm32g474_af.csv +++ b/ports/stm32/boards/stm32g474_af.csv @@ -2,12 +2,12 @@ Port ,Pin ,AF0 ,AF1 ,AF2 , ,I2C4/SYS_AF ,LPTIM1/TIM2/5/15/16/17,I2C1/3/TIM1/2/3/4/5/8/20/15/COMP1,QUADSPI1/I2C3/4/SAI1/USB/HRTIM1/TIM8/20/15/COMP3,I2C1/2/3/4/TIM1/8/16/17,QUADSPI1/SPI1/2/3/4/I2S2/3/I2C4/UART4/5/TIM8/Infrared,QUADSPI1/SPI2/3/I2S2/3/TIM1/5/8/20/Infrared,USART1/2/3/CAN/COMP7/5/6,I2C3/4/UART4/5/LPUART1/COMP1/2/7/4/5/6/3,CAN/TIM1/8/15/CAN1/2,QUADSPI1/TIM2/3/4/8/17,LPTIM1/TIM1/8/CAN1/3,FMC/LPUART1/SAI1/HRTIM1/TIM1,SAI1SAI1/HRTIM1/OPAMP2,UART4/5/SAI1/TIM2/15/UCPD1,SYS ,ADC PortA,PA0 , ,TIM2_CH1 ,TIM5_CH1 , , , , ,USART2_CTS ,COMP1_OUT ,TIM8_BKIN ,TIM8_ETR , , , ,TIM2_ETR ,EVENTOUT,ADC12_IN1 PortA,PA1 ,RTC_REFIN ,TIM2_CH2 ,TIM5_CH2 , , , , ,USART2_RTS_DE , ,TIM15_CH1N , , , , , ,EVENTOUT,ADC12_IN2 -PortA,PA2 , ,TIM2_CH3 ,TIM5_CH3 , , , , ,USART2_TX ,COMP2_OUT ,TIM15_CH1 ,QUADSPI1_BK1_NCS , ,LPUART1_TX , ,UCPD1_FRSTX ,EVENTOUT,ADC1_IN3 -PortA,PA3 , ,TIM2_CH4 ,TIM5_CH4 ,SAI1_CK1 , , , ,USART2_RX , ,TIM15_CH2 ,QUADSPI1_CLK , ,LPUART1_RX ,SAI1_MCLK_A , ,EVENTOUT,ADC1_IN4 +PortA,PA2 , ,TIM2_CH3 ,TIM5_CH3 , , , , ,USART2_TX ,COMP2_OUT ,TIM15_CH1 ,QUADSPI_BK1_NCS , ,LPUART1_TX , ,UCPD1_FRSTX ,EVENTOUT,ADC1_IN3 +PortA,PA3 , ,TIM2_CH4 ,TIM5_CH4 ,SAI1_CK1 , , , ,USART2_RX , ,TIM15_CH2 ,QUADSPI_CLK , ,LPUART1_RX ,SAI1_MCLK_A , ,EVENTOUT,ADC1_IN4 PortA,PA4 , , ,TIM3_CH2 , , ,SPI1_NSS ,SPI3_NSS/I2S3_WS ,USART2_CK , , , , , ,SAI1_FS_B , ,EVENTOUT,ADC2_IN17 PortA,PA5 , ,TIM2_CH1 ,TIM2_ETR , , ,SPI1_SCK , , , , , , , , ,UCPD1_FRSTX ,EVENTOUT,ADC2_IN13 -PortA,PA6 , ,TIM16_CH1 ,TIM3_CH1 , ,TIM8_BKIN ,SPI1_MISO ,TIM1_BKIN , ,COMP1_OUT , ,QUADSPI1_BK1_IO3 , ,LPUART1_CTS , , ,EVENTOUT,ADC2_IN3 -PortA,PA7 , ,TIM17_CH1 ,TIM3_CH2 , ,TIM8_CH1N ,SPI1_MOSI ,TIM1_CH1N , ,COMP2_OUT , ,QUADSPI1_BK1_IO2 , , , ,UCPD1_FRSTX ,EVENTOUT,ADC2_IN4 +PortA,PA6 , ,TIM16_CH1 ,TIM3_CH1 , ,TIM8_BKIN ,SPI1_MISO ,TIM1_BKIN , ,COMP1_OUT , ,QUADSPI_BK1_IO3 , ,LPUART1_CTS , , ,EVENTOUT,ADC2_IN3 +PortA,PA7 , ,TIM17_CH1 ,TIM3_CH2 , ,TIM8_CH1N ,SPI1_MOSI ,TIM1_CH1N , ,COMP2_OUT , ,QUADSPI_BK1_IO2 , , , ,UCPD1_FRSTX ,EVENTOUT,ADC2_IN4 PortA,PA8 ,MCO , ,I2C3_SCL , ,I2C2_SDA ,I2S2_MCK ,TIM1_CH1 ,USART1_CK ,COMP7_OUT , ,TIM4_ETR ,CAN3_RX ,SAI1_CK2 ,HRTIM1_CHA1 ,SAI1_SCK_A ,EVENTOUT,ADC5_IN1 PortA,PA9 , , ,I2C3_SMBA , ,I2C2_SCL ,I2S3_MCK ,TIM1_CH2 ,USART1_TX ,COMP5_OUT ,TIM15_BKIN ,TIM2_CH3 , , ,HRTIM1_CHA2 ,SAI1_FS_A ,EVENTOUT,ADC5_IN2 PortA,PA10, ,TIM17_BKIN , ,USB_CRS_SYNC ,I2C2_SMBA ,SPI2_MISO ,TIM1_CH3 ,USART1_RX ,COMP6_OUT , ,TIM2_CH4 ,TIM8_BKIN ,SAI1_D1 ,HRTIM1_CHB1 ,SAI1_SD_A ,EVENTOUT, @@ -16,9 +16,9 @@ PortA,PA12, ,TIM16_CH1 , PortA,PA13,SWDIOJTMS ,TIM16_CH1N , ,I2C4_SCL ,I2C1_SCL ,IR_OUT , ,USART3_CTS , , ,TIM4_CH3 , , ,SAI1_SD_B , ,EVENTOUT, PortA,PA14,SWCLKJTCK ,LPTIM1_OUT , ,I2C4_SMBA ,I2C1_SDA ,TIM8_CH2 ,TIM1_BKIN ,USART2_TX , , , , , ,SAI1_FS_B , ,EVENTOUT, PortA,PA15,JTDI ,TIM2_CH1 ,TIM8_CH1 , ,I2C1_SCL ,SPI1_NSS ,SPI3_NSS/I2S3_WS ,USART2_RX ,UART4_RTS_DE ,TIM1_BKIN , ,CAN3_TX , ,HRTIM1_FLT2 ,TIM2_ETR ,EVENTOUT, -PortB,PB0 , , ,TIM3_CH3 , ,TIM8_CH2N , ,TIM1_CH2N , , , ,QUADSPI1_BK1_IO1 , , ,HRTIM1_FLT5 ,UCPD1_FRSTX ,EVENTOUT,ADC3_IN12/ADC1_IN15 -PortB,PB1 , , ,TIM3_CH4 , ,TIM8_CH3N , ,TIM1_CH3N , ,COMP4_OUT , ,QUADSPI1_BK1_IO0 , ,LPUART1_RTS_DE ,HRTIM1_SCOUT , ,EVENTOUT,ADC3_IN1/ADC1_IN12 -PortB,PB2 ,RTC_OUT2 ,LPTIM1_OUT ,TIM5_CH1 ,TIM20_CH1 ,I2C3_SMBA , , , , , ,QUADSPI1_BK2_IO1 , , ,HRTIM1_SCIN , ,EVENTOUT,ADC2_IN12 +PortB,PB0 , , ,TIM3_CH3 , ,TIM8_CH2N , ,TIM1_CH2N , , , ,QUADSPI_BK1_IO1 , , ,HRTIM1_FLT5 ,UCPD1_FRSTX ,EVENTOUT,ADC3_IN12/ADC1_IN15 +PortB,PB1 , , ,TIM3_CH4 , ,TIM8_CH3N , ,TIM1_CH3N , ,COMP4_OUT , ,QUADSPI_BK1_IO0 , ,LPUART1_RTS_DE ,HRTIM1_SCOUT , ,EVENTOUT,ADC3_IN1/ADC1_IN12 +PortB,PB2 ,RTC_OUT2 ,LPTIM1_OUT ,TIM5_CH1 ,TIM20_CH1 ,I2C3_SMBA , , , , , ,QUADSPI_BK2_IO1 , , ,HRTIM1_SCIN , ,EVENTOUT,ADC2_IN12 PortB,PB3 ,JTDOTRACESWO,TIM2_CH2 ,TIM4_ETR ,USB_CRS_SYNC ,TIM8_CH1N ,SPI1_SCK ,SPI3_SCK/I2S3_CK ,USART2_TX , , ,TIM3_ETR ,CAN3_RX ,HRTIM1_SCOUT ,HRTIM1_EEV9 ,SAI1_SCK_B ,EVENTOUT, PortB,PB4 ,JTRST ,TIM16_CH1 ,TIM3_CH1 , ,TIM8_CH2N ,SPI1_MISO ,SPI3_MISO ,USART2_RX ,UART5_RTS_DE , ,TIM17_BKIN ,CAN3_TX , ,HRTIM1_EEV7 ,SAI1_MCLK_B ,EVENTOUT, PortB,PB5 , ,TIM16_BKIN ,TIM3_CH2 ,TIM8_CH3N ,I2C1_SMBA ,SPI1_MOSI ,SPI3_MOSI/I2S3_SD ,USART2_CK ,I2C3_SDA ,CAN2_RX ,TIM17_CH1 ,LPTIM1_IN1 ,SAI1_SD_B ,HRTIM1_EEV6 ,UART5_CTS ,EVENTOUT, @@ -26,17 +26,17 @@ PortB,PB6 , ,TIM16_CH1N ,TIM4_CH1 PortB,PB7 , ,TIM17_CH1N ,TIM4_CH2 ,I2C4_SDA ,I2C1_SDA ,TIM8_BKIN , ,USART1_RX ,COMP3_OUT , ,TIM3_CH4 ,LPTIM1_IN2 ,FMC_NL ,HRTIM1_EEV3 ,UART4_CTS ,EVENTOUT, PortB,PB8 , ,TIM16_CH1 ,TIM4_CH3 ,SAI1_CK1 ,I2C1_SCL , , ,USART3_RX ,COMP1_OUT ,CAN1_RX ,TIM8_CH2 , ,TIM1_BKIN ,HRTIM1_EEV8 ,SAI1_MCLK_A ,EVENTOUT, PortB,PB9 , ,TIM17_CH1 ,TIM4_CH4 ,SAI1_D2 ,I2C1_SDA , ,IR_OUT ,USART3_TX ,COMP2_OUT ,CAN1_TX ,TIM8_CH3 , ,TIM1_CH3N ,HRTIM1_EEV5 ,SAI1_FS_A ,EVENTOUT, -PortB,PB10, ,TIM2_CH3 , , , , , ,USART3_TX ,LPUART1_RX , ,QUADSPI1_CLK , ,TIM1_BKIN ,HRTIM1_FLT3 ,SAI1_SCK_A ,EVENTOUT, -PortB,PB11, ,TIM2_CH4 , , , , , ,USART3_RX ,LPUART1_TX , ,QUADSPI1_BK1_NCS , , ,HRTIM1_FLT4 , ,EVENTOUT,ADC12_IN14 +PortB,PB10, ,TIM2_CH3 , , , , , ,USART3_TX ,LPUART1_RX , ,QUADSPI_CLK , ,TIM1_BKIN ,HRTIM1_FLT3 ,SAI1_SCK_A ,EVENTOUT, +PortB,PB11, ,TIM2_CH4 , , , , , ,USART3_RX ,LPUART1_TX , ,QUADSPI_BK1_NCS , , ,HRTIM1_FLT4 , ,EVENTOUT,ADC12_IN14 PortB,PB12, , ,TIM5_ETR , ,I2C2_SMBA ,SPI2_NSS/I2S2_WS ,TIM1_BKIN ,USART3_CK ,LPUART1_RTS_DE ,CAN2_RX , , , ,HRTIM1_CHC1 , ,EVENTOUT,ADC4_IN3/ADC1_IN11 PortB,PB13, , , , , ,SPI2_SCK/I2S2_CK ,TIM1_CH1N ,USART3_CTS ,LPUART1_CTS ,CAN2_TX , , , ,HRTIM1_CHC2 , ,EVENTOUT,ADC3_IN5 PortB,PB14, ,TIM15_CH1 , , , ,SPI2_MISO ,TIM1_CH2N ,USART3_RTS_DE ,COMP4_OUT , , , , ,HRTIM1_CHD1 , ,EVENTOUT,ADC4_IN4/ADC1_IN5 PortB,PB15,RTC_REFIN ,TIM15_CH2 ,TIM15_CH1N ,COMP3_OUT ,TIM1_CH3N ,SPI2_MOSI/I2S2_SD , , , , , , , ,HRTIM1_CHD2 , ,EVENTOUT,ADC4_IN5/ADC2_IN15 PortC,PC0 , ,LPTIM1_IN1 ,TIM1_CH1 , , , , , ,LPUART1_RX , , , , , , ,EVENTOUT,ADC12_IN6 -PortC,PC1 , ,LPTIM1_OUT ,TIM1_CH2 , , , , , ,LPUART1_TX , ,QUADSPI1_BK2_IO0 , , ,SAI1_SD_A , ,EVENTOUT,ADC12_IN7 -PortC,PC2 , ,LPTIM1_IN2 ,TIM1_CH3 ,COMP3_OUT , , ,TIM20_CH2 , , , ,QUADSPI1_BK2_IO1 , , , , ,EVENTOUT,ADC12_IN8 -PortC,PC3 , ,LPTIM1_ETR ,TIM1_CH4 ,SAI1_D1 , , ,TIM1_BKIN2 , , , ,QUADSPI1_BK2_IO2 , , ,SAI1_SD_A , ,EVENTOUT,ADC12_IN9 -PortC,PC4 , , ,TIM1_ETR , ,I2C2_SCL , , ,USART1_TX , , ,QUADSPI1_BK2_IO3 , , , , ,EVENTOUT,ADC2_IN5 +PortC,PC1 , ,LPTIM1_OUT ,TIM1_CH2 , , , , , ,LPUART1_TX , ,QUADSPI_BK2_IO0 , , ,SAI1_SD_A , ,EVENTOUT,ADC12_IN7 +PortC,PC2 , ,LPTIM1_IN2 ,TIM1_CH3 ,COMP3_OUT , , ,TIM20_CH2 , , , ,QUADSPI_BK2_IO1 , , , , ,EVENTOUT,ADC12_IN8 +PortC,PC3 , ,LPTIM1_ETR ,TIM1_CH4 ,SAI1_D1 , , ,TIM1_BKIN2 , , , ,QUADSPI_BK2_IO2 , , ,SAI1_SD_A , ,EVENTOUT,ADC12_IN9 +PortC,PC4 , , ,TIM1_ETR , ,I2C2_SCL , , ,USART1_TX , , ,QUADSPI_BK2_IO3 , , , , ,EVENTOUT,ADC2_IN5 PortC,PC5 , , ,TIM15_BKIN ,SAI1_D3 , , ,TIM1_CH4N ,USART1_RX , , , , , ,HRTIM1_EEV10 , ,EVENTOUT,ADC2_IN11 PortC,PC6 , , ,TIM3_CH1 ,HRTIM1_EEV10 ,TIM8_CH1 , ,I2S2_MCK ,COMP6_OUT ,I2C4_SCL , , , , ,HRTIM1_CHF1 , ,EVENTOUT, PortC,PC7 , , ,TIM3_CH2 ,HRTIM1_FLT5 ,TIM8_CH2 , ,I2S3_MCK ,COMP5_OUT ,I2C4_SDA , , , , ,HRTIM1_CHF2 , ,EVENTOUT, @@ -51,11 +51,11 @@ PortC,PC15, , , PortD,PD0 , , , , , , ,TIM8_CH4N , , ,CAN1_RX , , ,FMC_D2 , , ,EVENTOUT, PortD,PD1 , , , , ,TIM8_CH4 , ,TIM8_BKIN2 , , ,CAN1_TX , , ,FMC_D3 , , ,EVENTOUT, PortD,PD2 , , ,TIM3_ETR , ,TIM8_BKIN ,UART5_RX , , , , , , , , , ,EVENTOUT, -PortD,PD3 , , ,TIM2_CH1/TIM2_ETR , , , , ,USART2_CTS , , ,QUADSPI1_BK2_NCS , ,FMC_CLK , , ,EVENTOUT, -PortD,PD4 , , ,TIM2_CH2 , , , , ,USART2_RTS_DE , , ,QUADSPI1_BK2_IO0 , ,FMC_NOE , , ,EVENTOUT, -PortD,PD5 , , , , , , , ,USART2_TX , , ,QUADSPI1_BK2_IO1 , ,FMC_NWE , , ,EVENTOUT, -PortD,PD6 , , ,TIM2_CH4 ,SAI1_D1 , , , ,USART2_RX , , ,QUADSPI1_BK2_IO2 , ,FMC_NWAIT ,SAI1_SD_A , ,EVENTOUT, -PortD,PD7 , , ,TIM2_CH3 , , , , ,USART2_CK , , ,QUADSPI1_BK2_IO3 , ,FMC_NCE/FMC_NE1 , , ,EVENTOUT, +PortD,PD3 , , ,TIM2_CH1/TIM2_ETR , , , , ,USART2_CTS , , ,QUADSPI_BK2_NCS , ,FMC_CLK , , ,EVENTOUT, +PortD,PD4 , , ,TIM2_CH2 , , , , ,USART2_RTS_DE , , ,QUADSPI_BK2_IO0 , ,FMC_NOE , , ,EVENTOUT, +PortD,PD5 , , , , , , , ,USART2_TX , , ,QUADSPI_BK2_IO1 , ,FMC_NWE , , ,EVENTOUT, +PortD,PD6 , , ,TIM2_CH4 ,SAI1_D1 , , , ,USART2_RX , , ,QUADSPI_BK2_IO2 , ,FMC_NWAIT ,SAI1_SD_A , ,EVENTOUT, +PortD,PD7 , , ,TIM2_CH3 , , , , ,USART2_CK , , ,QUADSPI_BK2_IO3 , ,FMC_NCE/FMC_NE1 , , ,EVENTOUT, PortD,PD8 , , , , , , , ,USART3_TX , , , , ,FMC_D13 , , ,EVENTOUT,ADC4_IN12/ADC5_IN12 PortD,PD9 , , , , , , , ,USART3_RX , , , , ,FMC_D14 , , ,EVENTOUT,ADC4_IN13/ADC5_IN13 PortD,PD10, , , , , , , ,USART3_CK , , , , ,FMC_D15 , , ,EVENTOUT,ADC345_IN7 @@ -74,23 +74,23 @@ PortE,PE6 ,TRACED3 , , PortE,PE7 , , ,TIM1_ETR , , , , , , , , , ,FMC_D4 ,SAI1_SD_B , ,EVENTOUT,ADC3_IN4 PortE,PE8 , ,TIM5_CH3 ,TIM1_CH1N , , , , , , , , , ,FMC_D5 ,SAI1_SCK_B , ,EVENTOUT,ADC345_IN6 PortE,PE9 , ,TIM5_CH4 ,TIM1_CH1 , , , , , , , , , ,FMC_D6 ,SAI1_FS_B , ,EVENTOUT,ADC3_IN2 -PortE,PE10, , ,TIM1_CH2N , , , , , , , ,QUADSPI1_CLK , ,FMC_D7 ,SAI1_MCLK_B , ,EVENTOUT,ADC345_IN14 -PortE,PE11, , ,TIM1_CH2 , , ,SPI4_NSS , , , , ,QUADSPI1_BK1_NCS , ,FMC_D8 , , ,EVENTOUT,ADC345_IN15 -PortE,PE12, , ,TIM1_CH3N , , ,SPI4_SCK , , , , ,QUADSPI1_BK1_IO0 , ,FMC_D9 , , ,EVENTOUT,ADC345_IN16 -PortE,PE13, , ,TIM1_CH3 , , ,SPI4_MISO , , , , ,QUADSPI1_BK1_IO1 , ,FMC_D10 , , ,EVENTOUT,ADC3_IN3 -PortE,PE14, , ,TIM1_CH4 , , ,SPI4_MOSI ,TIM1_BKIN2 , , , ,QUADSPI1_BK1_IO2 , ,FMC_D11 , , ,EVENTOUT,ADC4_IN1 -PortE,PE15, , ,TIM1_BKIN , , , ,TIM1_CH4N ,USART3_RX , , ,QUADSPI1_BK1_IO3 , ,FMC_D12 , , ,EVENTOUT,ADC4_IN2 +PortE,PE10, , ,TIM1_CH2N , , , , , , , ,QUADSPI_CLK , ,FMC_D7 ,SAI1_MCLK_B , ,EVENTOUT,ADC345_IN14 +PortE,PE11, , ,TIM1_CH2 , , ,SPI4_NSS , , , , ,QUADSPI_BK1_NCS , ,FMC_D8 , , ,EVENTOUT,ADC345_IN15 +PortE,PE12, , ,TIM1_CH3N , , ,SPI4_SCK , , , , ,QUADSPI_BK1_IO0 , ,FMC_D9 , , ,EVENTOUT,ADC345_IN16 +PortE,PE13, , ,TIM1_CH3 , , ,SPI4_MISO , , , , ,QUADSPI_BK1_IO1 , ,FMC_D10 , , ,EVENTOUT,ADC3_IN3 +PortE,PE14, , ,TIM1_CH4 , , ,SPI4_MOSI ,TIM1_BKIN2 , , , ,QUADSPI_BK1_IO2 , ,FMC_D11 , , ,EVENTOUT,ADC4_IN1 +PortE,PE15, , ,TIM1_BKIN , , , ,TIM1_CH4N ,USART3_RX , , ,QUADSPI_BK1_IO3 , ,FMC_D12 , , ,EVENTOUT,ADC4_IN2 PortF,PF0 , , , , ,I2C2_SDA ,SPI2_NSS/I2S2_WS ,TIM1_CH3N , , , , , , , , ,EVENTOUT,ADC1_IN10 PortF,PF1 , , , , , ,SPI2_SCK/I2S2_CK , , , , , , , , , ,EVENTOUT,ADC2_IN10 PortF,PF2 , , ,TIM20_CH3 , ,I2C2_SMBA , , , , , , , ,FMC_A2 , , ,EVENTOUT, PortF,PF3 , , ,TIM20_CH4 , ,I2C3_SCL , , , , , , , ,FMC_A3 , , ,EVENTOUT, PortF,PF4 , , ,COMP1_OUT ,TIM20_CH1N ,I2C3_SDA , , , , , , , ,FMC_A4 , , ,EVENTOUT, PortF,PF5 , , ,TIM20_CH2N , , , , , , , , , ,FMC_A5 , , ,EVENTOUT, -PortF,PF6 , ,TIM5_ETR ,TIM4_CH4 ,SAI1_SD_B ,I2C2_SCL , ,TIM5_CH1 ,USART3_RTS , , ,QUADSPI1_BK1_IO3 , , , , ,EVENTOUT, -PortF,PF7 , , ,TIM20_BKIN , , , ,TIM5_CH2 , , , ,QUADSPI1_BK1_IO2 , ,FMC_A1 ,SAI1_MCLK_B , ,EVENTOUT, -PortF,PF8 , , ,TIM20_BKIN2 , , , ,TIM5_CH3 , , , ,QUADSPI1_BK1_IO0 , ,FMC_A24 ,SAI1_SCK_B , ,EVENTOUT, -PortF,PF9 , , ,TIM20_BKIN ,TIM15_CH1 , ,SPI2_SCK ,TIM5_CH4 , , , ,QUADSPI1_BK1_IO1 , ,FMC_A25 ,SAI1_FS_B , ,EVENTOUT, -PortF,PF10, , ,TIM20_BKIN2 ,TIM15_CH2 , ,SPI2_SCK , , , , ,QUADSPI1_CLK , ,FMC_A0 ,SAI1_D3 , ,EVENTOUT, +PortF,PF6 , ,TIM5_ETR ,TIM4_CH4 ,SAI1_SD_B ,I2C2_SCL , ,TIM5_CH1 ,USART3_RTS , , ,QUADSPI_BK1_IO3 , , , , ,EVENTOUT, +PortF,PF7 , , ,TIM20_BKIN , , , ,TIM5_CH2 , , , ,QUADSPI_BK1_IO2 , ,FMC_A1 ,SAI1_MCLK_B , ,EVENTOUT, +PortF,PF8 , , ,TIM20_BKIN2 , , , ,TIM5_CH3 , , , ,QUADSPI_BK1_IO0 , ,FMC_A24 ,SAI1_SCK_B , ,EVENTOUT, +PortF,PF9 , , ,TIM20_BKIN ,TIM15_CH1 , ,SPI2_SCK ,TIM5_CH4 , , , ,QUADSPI_BK1_IO1 , ,FMC_A25 ,SAI1_FS_B , ,EVENTOUT, +PortF,PF10, , ,TIM20_BKIN2 ,TIM15_CH2 , ,SPI2_SCK , , , , ,QUADSPI_CLK , ,FMC_A0 ,SAI1_D3 , ,EVENTOUT, PortF,PF11, , ,TIM20_ETR , , , , , , , , , ,FMC_NE4 , , ,EVENTOUT, PortF,PF12, , ,TIM20_CH1 , , , , , , , , , ,FMC_A6 , , ,EVENTOUT, PortF,PF13, , ,TIM20_CH2 , ,I2C4_SMBA , , , , , , , ,FMC_A7 , , ,EVENTOUT, diff --git a/ports/stm32/mpu.h b/ports/stm32/mpu.h index a87c04a58df5c..5756cb0560dde 100644 --- a/ports/stm32/mpu.h +++ b/ports/stm32/mpu.h @@ -28,7 +28,7 @@ #include "irq.h" -#if (defined(STM32F4) && defined(MICROPY_HW_ETH_MDC)) || defined(STM32F7) || defined(STM32H7) || defined(STM32WB) +#if (defined(STM32F4) && defined(MICROPY_HW_ETH_MDC)) || defined(STM32F7) || defined(STM32G4) || defined(STM32H7) || defined(STM32WB) #define MPU_REGION_ETH (MPU_REGION_NUMBER0) #define MPU_REGION_QSPI1 (MPU_REGION_NUMBER1) diff --git a/ports/stm32/qspi.c b/ports/stm32/qspi.c index 781aae803ef21..2ef9a4d0187b0 100644 --- a/ports/stm32/qspi.c +++ b/ports/stm32/qspi.c @@ -213,11 +213,13 @@ static int qspi_ioctl(void *self_in, uint32_t cmd, uintptr_t arg) { qspi_memory_map(); break; case MP_QSPI_IOCTL_MEMORY_MODIFIED: { + #if defined(__ICACHE_PRESENT) && (__ICACHE_PRESENT == 1U) uintptr_t *addr_len = (uintptr_t *)arg; volatile void *addr = (volatile void *)(QSPI_MAP_ADDR + addr_len[0]); size_t len = addr_len[1]; SCB_InvalidateICache_by_Addr(addr, len); SCB_InvalidateDCache_by_Addr(addr, len); + #endif break; } }