From 8b4a21cd64c8199601629e6ae08c746f85fdd068 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 4 Mar 2024 12:11:27 +0100 Subject: [PATCH 01/49] extmod/network_ninaw10: Activate the NIC on demand. Activate the NIC on calls to connect() or config() if it's not already active. This change makes the NINA NIC more in line with CYW43 and other NICs, which allow configuring the NIC before or after it is activated. Signed-off-by: iabdalkader --- extmod/network_ninaw10.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/extmod/network_ninaw10.c b/extmod/network_ninaw10.c index e68a7a66e2..e53061557c 100644 --- a/extmod/network_ninaw10.c +++ b/extmod/network_ninaw10.c @@ -193,8 +193,7 @@ static mp_obj_t network_ninaw10_active(size_t n_args, const mp_obj_t *args) { nina_obj_t *self = MP_OBJ_TO_PTR(args[0]); if (n_args == 2) { bool active = mp_obj_is_true(args[1]); - network_ninaw10_deinit(); - if (active) { + if (active && !self->active) { int error = 0; if ((error = nina_init()) != 0) { mp_raise_msg_varg(&mp_type_OSError, @@ -223,7 +222,8 @@ static mp_obj_t network_ninaw10_active(size_t n_args, const mp_obj_t *args) { semver[NINA_FW_VER_MINOR_OFFS] - 48, semver[NINA_FW_VER_PATCH_OFFS] - 48); } soft_timer_static_init(&mp_wifi_poll_timer, SOFT_TIMER_MODE_ONE_SHOT, 0, network_ninaw10_timer_callback); - } else { + } else if (!active && self->active) { + network_ninaw10_deinit(); nina_deinit(); } self->active = active; @@ -289,6 +289,11 @@ static mp_obj_t network_ninaw10_connect(mp_uint_t n_args, const mp_obj_t *pos_ar mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Key can't be empty!")); } + // Activate the interface if not active. + if (!self->active) { + network_ninaw10_active(2, (mp_obj_t [2]) { pos_args[0], mp_const_true }); + } + // Disconnect active connections first. if (nina_isconnected()) { nina_disconnect(); From 2b6f81f2b9d5135774d1b636802194f9666678d5 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 4 Mar 2024 12:13:37 +0100 Subject: [PATCH 02/49] extmod/network_ninaw10: Set the proper security mode if none provided. If no security mode is provided, use WPA for station and WEP for AP. Note only WEP is supported in AP mode. Signed-off-by: iabdalkader --- extmod/network_ninaw10.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/extmod/network_ninaw10.c b/extmod/network_ninaw10.c index e53061557c..6a60faeae9 100644 --- a/extmod/network_ninaw10.c +++ b/extmod/network_ninaw10.c @@ -260,7 +260,7 @@ static mp_obj_t network_ninaw10_connect(mp_uint_t n_args, const mp_obj_t *pos_ar static const mp_arg_t allowed_args[] = { { MP_QSTR_ssid, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_key, MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_security, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = NINA_SEC_WPA_PSK} }, + { MP_QSTR_security, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_channel, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, }; @@ -276,15 +276,21 @@ static mp_obj_t network_ninaw10_connect(mp_uint_t n_args, const mp_obj_t *pos_ar mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("SSID can't be empty!")); } - // get key and sec + // get encryption key const char *key = NULL; - mp_uint_t security = NINA_SEC_OPEN; - if (args[ARG_key].u_obj != mp_const_none) { key = mp_obj_str_get_str(args[ARG_key].u_obj); - security = args[ARG_security].u_int; } + // get security mode + mp_uint_t security = args[ARG_security].u_int; + if (security == -1 && self->itf == MOD_NETWORK_STA_IF) { + security = NINA_SEC_WPA_PSK; + } else if (security == -1 && self->itf == MOD_NETWORK_AP_IF) { + security = NINA_SEC_WEP; + } + + // Ensure that the key is not empty if a security mode is used. if (security != NINA_SEC_OPEN && strlen(key) == 0) { mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Key can't be empty!")); } @@ -316,7 +322,7 @@ static mp_obj_t network_ninaw10_connect(mp_uint_t n_args, const mp_obj_t *pos_ar mp_uint_t channel = args[ARG_channel].u_int; if (security != NINA_SEC_OPEN && security != NINA_SEC_WEP) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("AP mode supports WEP security only.")); + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("AP mode only supports WEP or OPEN security modes")); } // Initialize WiFi in AP mode. From c231c896519fa1b925d4465ed3d4bea437758854 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 14 Mar 2024 10:27:37 +0100 Subject: [PATCH 03/49] extmod/network_ninaw10: Fix error messages. Signed-off-by: iabdalkader --- extmod/network_ninaw10.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/extmod/network_ninaw10.c b/extmod/network_ninaw10.c index 6a60faeae9..90cdafc7a4 100644 --- a/extmod/network_ninaw10.c +++ b/extmod/network_ninaw10.c @@ -153,7 +153,7 @@ static void network_ninaw10_poll_connect(mp_sched_node_t *node) { debug_printf("poll_connect() status: %d reason %d\n", status, reason); if (nina_connect(self->ssid, self->security, self->key, 0) != 0) { mp_raise_msg_varg(&mp_type_OSError, - MP_ERROR_TEXT("could not connect to ssid=%s, sec=%d, key=%s\n"), + MP_ERROR_TEXT("could not connect to ssid=%s, sec=%d, key=%s"), self->ssid, self->security, self->key); } } else { @@ -197,14 +197,14 @@ static mp_obj_t network_ninaw10_active(size_t n_args, const mp_obj_t *args) { int error = 0; if ((error = nina_init()) != 0) { mp_raise_msg_varg(&mp_type_OSError, - MP_ERROR_TEXT("Failed to initialize Nina-W10 module, error: %d\n"), error); + MP_ERROR_TEXT("failed to initialize Nina-W10 module, error: %d"), error); } // check firmware version uint8_t semver[NINA_FW_VER_LEN]; if (nina_fw_version(semver) != 0) { nina_deinit(); mp_raise_msg_varg(&mp_type_OSError, - MP_ERROR_TEXT("Failed to read firmware version, error: %d\n"), error); + MP_ERROR_TEXT("failed to read firmware version, error: %d"), error); } // Check the minimum supported firmware version. uint32_t fwmin = (NINA_FW_VER_MIN_MAJOR * 100) + @@ -217,7 +217,7 @@ static mp_obj_t network_ninaw10_active(size_t n_args, const mp_obj_t *args) { if (fwver < fwmin) { mp_raise_msg_varg(&mp_type_OSError, - MP_ERROR_TEXT("Firmware version mismatch. Minimum supported firmware is v%d.%d.%d found v%d.%d.%d\n"), + MP_ERROR_TEXT("firmware version mismatch, minimum supported firmware is v%d.%d.%d found v%d.%d.%d"), NINA_FW_VER_MIN_MAJOR, NINA_FW_VER_MIN_MINOR, NINA_FW_VER_MIN_PATCH, semver[NINA_FW_VER_MAJOR_OFFS] - 48, semver[NINA_FW_VER_MINOR_OFFS] - 48, semver[NINA_FW_VER_PATCH_OFFS] - 48); } @@ -273,7 +273,7 @@ static mp_obj_t network_ninaw10_connect(mp_uint_t n_args, const mp_obj_t *pos_ar const char *ssid = mp_obj_str_get_str(args[ARG_ssid].u_obj); if (strlen(ssid) == 0) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("SSID can't be empty!")); + mp_raise_ValueError(MP_ERROR_TEXT("SSID can't be empty")); } // get encryption key @@ -292,7 +292,7 @@ static mp_obj_t network_ninaw10_connect(mp_uint_t n_args, const mp_obj_t *pos_ar // Ensure that the key is not empty if a security mode is used. if (security != NINA_SEC_OPEN && strlen(key) == 0) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Key can't be empty!")); + mp_raise_ValueError(MP_ERROR_TEXT("key can't be empty")); } // Activate the interface if not active. @@ -309,7 +309,7 @@ static mp_obj_t network_ninaw10_connect(mp_uint_t n_args, const mp_obj_t *pos_ar // Initialize WiFi in Station mode. if (nina_connect(ssid, security, key, 0) != 0) { mp_raise_msg_varg(&mp_type_OSError, - MP_ERROR_TEXT("could not connect to ssid=%s, sec=%d, key=%s\n"), ssid, security, key); + MP_ERROR_TEXT("could not connect to ssid=%s, sec=%d, key=%s"), ssid, security, key); } // Save connection info to re-connect if needed. From 23ccbcf23043be5e621a4e49b7dd4549af4239fc Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 14 Mar 2024 10:58:41 +1100 Subject: [PATCH 04/49] extmod/modmachine: Add MICROPY_PY_MACHINE_SIGNAL configuration option. Enabled by default. Signed-off-by: Damien George --- extmod/machine_signal.c | 9 ++++----- extmod/modmachine.c | 2 ++ py/mpconfig.h | 5 +++++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/extmod/machine_signal.c b/extmod/machine_signal.c index 6295a496b3..d2e7bc58ca 100644 --- a/extmod/machine_signal.c +++ b/extmod/machine_signal.c @@ -24,12 +24,11 @@ * THE SOFTWARE. */ -#include "py/mpconfig.h" -#if MICROPY_PY_MACHINE - #include - #include "py/runtime.h" + +#if MICROPY_PY_MACHINE_SIGNAL + #include "extmod/modmachine.h" #include "extmod/virtpin.h" @@ -181,4 +180,4 @@ MP_DEFINE_CONST_OBJ_TYPE( locals_dict, &signal_locals_dict ); -#endif // MICROPY_PY_MACHINE +#endif // MICROPY_PY_MACHINE_SIGNAL diff --git a/extmod/modmachine.c b/extmod/modmachine.c index 90e2a38a73..6fb6b9eaf5 100644 --- a/extmod/modmachine.c +++ b/extmod/modmachine.c @@ -186,7 +186,9 @@ static const mp_rom_map_elem_t machine_module_globals_table[] = { #if MICROPY_PY_MACHINE_PIN_BASE { MP_ROM_QSTR(MP_QSTR_PinBase), MP_ROM_PTR(&machine_pinbase_type) }, #endif + #if MICROPY_PY_MACHINE_SIGNAL { MP_ROM_QSTR(MP_QSTR_Signal), MP_ROM_PTR(&machine_signal_type) }, + #endif // Classes for software bus protocols. #if MICROPY_PY_MACHINE_SOFTI2C diff --git a/py/mpconfig.h b/py/mpconfig.h index 90f8e592bf..8b49c9ca08 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1702,6 +1702,11 @@ typedef double mp_float_t; #define MICROPY_PY_MACHINE_PULSE (0) #endif +// Whether to provide the "machine.Signal" class +#ifndef MICROPY_PY_MACHINE_SIGNAL +#define MICROPY_PY_MACHINE_SIGNAL (MICROPY_PY_MACHINE) +#endif + #ifndef MICROPY_PY_MACHINE_I2C #define MICROPY_PY_MACHINE_I2C (0) #endif From dd134e4836ceee23234b35279bb120259b350787 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 14 Mar 2024 11:01:33 +1100 Subject: [PATCH 05/49] extmod/modmachine: Add MICROPY_PY_MACHINE_MEMX configuration option. Enabled by default. Signed-off-by: Damien George --- extmod/machine_mem.c | 4 ++-- extmod/modmachine.c | 2 ++ py/mpconfig.h | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/extmod/machine_mem.c b/extmod/machine_mem.c index c8f0889b9a..c34ece2454 100644 --- a/extmod/machine_mem.c +++ b/extmod/machine_mem.c @@ -27,7 +27,7 @@ #include "py/runtime.h" #include "extmod/modmachine.h" -#if MICROPY_PY_MACHINE +#if MICROPY_PY_MACHINE_MEMX // If you wish to override the functions for mapping the machine_mem read/write // address, then add a #define for MICROPY_MACHINE_MEM_GET_READ_ADDR and/or @@ -113,4 +113,4 @@ const machine_mem_obj_t machine_mem8_obj = {{&machine_mem_type}, 1}; const machine_mem_obj_t machine_mem16_obj = {{&machine_mem_type}, 2}; const machine_mem_obj_t machine_mem32_obj = {{&machine_mem_type}, 4}; -#endif // MICROPY_PY_MACHINE +#endif // MICROPY_PY_MACHINE_MEMX diff --git a/extmod/modmachine.c b/extmod/modmachine.c index 6fb6b9eaf5..792652659a 100644 --- a/extmod/modmachine.c +++ b/extmod/modmachine.c @@ -138,9 +138,11 @@ static const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_machine) }, // Memory access objects. + #if MICROPY_PY_MACHINE_MEMX { MP_ROM_QSTR(MP_QSTR_mem8), MP_ROM_PTR(&machine_mem8_obj) }, { MP_ROM_QSTR(MP_QSTR_mem16), MP_ROM_PTR(&machine_mem16_obj) }, { MP_ROM_QSTR(MP_QSTR_mem32), MP_ROM_PTR(&machine_mem32_obj) }, + #endif // Miscellaneous functions. #if MICROPY_PY_MACHINE_BARE_METAL_FUNCS diff --git a/py/mpconfig.h b/py/mpconfig.h index 8b49c9ca08..182115ed71 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1702,6 +1702,11 @@ typedef double mp_float_t; #define MICROPY_PY_MACHINE_PULSE (0) #endif +// Whether to provide the "machine.mem8/16/32" objects +#ifndef MICROPY_PY_MACHINE_MEMX +#define MICROPY_PY_MACHINE_MEMX (MICROPY_PY_MACHINE) +#endif + // Whether to provide the "machine.Signal" class #ifndef MICROPY_PY_MACHINE_SIGNAL #define MICROPY_PY_MACHINE_SIGNAL (MICROPY_PY_MACHINE) From bfc3dde2c9cee31c85f838d0e89780378170551d Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 14 Mar 2024 11:09:14 +1100 Subject: [PATCH 06/49] extmod/modmachine: Add MICROPY_PY_MACHINE_RESET configuration option. Disabled by default, but enabled on all boards that previously had `MICROPY_PY_MACHINE_BARE_METAL_FUNCS` enabled. Signed-off-by: Damien George --- extmod/modmachine.c | 25 ++++++++++++++++--------- ports/cc3200/mpconfigport.h | 1 + ports/esp32/mpconfigport.h | 1 + ports/esp8266/mpconfigport.h | 1 + ports/mimxrt/mpconfigport.h | 1 + ports/nrf/mpconfigport.h | 1 + ports/renesas-ra/mpconfigport.h | 1 + ports/rp2/mpconfigport.h | 1 + ports/samd/mpconfigport.h | 1 + ports/stm32/mpconfigport.h | 1 + py/mpconfig.h | 5 +++++ 11 files changed, 30 insertions(+), 9 deletions(-) diff --git a/extmod/modmachine.c b/extmod/modmachine.c index 792652659a..d7e82b124d 100644 --- a/extmod/modmachine.c +++ b/extmod/modmachine.c @@ -43,10 +43,13 @@ static void mp_machine_idle(void); NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args); #endif -#if MICROPY_PY_MACHINE_BARE_METAL_FUNCS -static mp_obj_t mp_machine_unique_id(void); +#if MICROPY_PY_MACHINE_RESET NORETURN static void mp_machine_reset(void); static mp_int_t mp_machine_reset_cause(void); +#endif + +#if MICROPY_PY_MACHINE_BARE_METAL_FUNCS +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); @@ -77,12 +80,7 @@ static mp_obj_t machine_idle(void) { } static MP_DEFINE_CONST_FUN_OBJ_0(machine_idle_obj, machine_idle); -#if MICROPY_PY_MACHINE_BARE_METAL_FUNCS - -static mp_obj_t machine_unique_id(void) { - return mp_machine_unique_id(); -} -MP_DEFINE_CONST_FUN_OBJ_0(machine_unique_id_obj, machine_unique_id); +#if MICROPY_PY_MACHINE_RESET NORETURN static mp_obj_t machine_reset(void) { mp_machine_reset(); @@ -94,6 +92,15 @@ static mp_obj_t machine_reset_cause(void) { } MP_DEFINE_CONST_FUN_OBJ_0(machine_reset_cause_obj, machine_reset_cause); +#endif + +#if MICROPY_PY_MACHINE_BARE_METAL_FUNCS + +static mp_obj_t machine_unique_id(void) { + return mp_machine_unique_id(); +} +MP_DEFINE_CONST_FUN_OBJ_0(machine_unique_id_obj, machine_unique_id); + static mp_obj_t machine_freq(size_t n_args, const mp_obj_t *args) { if (n_args == 0) { return mp_machine_get_freq(); @@ -154,7 +161,7 @@ static const mp_rom_map_elem_t machine_module_globals_table[] = { #if MICROPY_PY_MACHINE_BOOTLOADER { MP_ROM_QSTR(MP_QSTR_bootloader), MP_ROM_PTR(&machine_bootloader_obj) }, #endif - #if MICROPY_PY_MACHINE_BARE_METAL_FUNCS + #if MICROPY_PY_MACHINE_RESET { MP_ROM_QSTR(MP_QSTR_reset), MP_ROM_PTR(&machine_reset_obj) }, { MP_ROM_QSTR(MP_QSTR_reset_cause), MP_ROM_PTR(&machine_reset_cause_obj) }, #endif diff --git a/ports/cc3200/mpconfigport.h b/ports/cc3200/mpconfigport.h index 9cbc1afc0b..f1ba4bedd0 100644 --- a/ports/cc3200/mpconfigport.h +++ b/ports/cc3200/mpconfigport.h @@ -127,6 +127,7 @@ #define MICROPY_PY_VFS (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/cc3200/mods/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_DISABLE_IRQ_ENABLE_IRQ (1) #define MICROPY_PY_MACHINE_WDT (1) diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index f004c78b0a..0afb12f85c 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -114,6 +114,7 @@ #define MICROPY_PY_OS_URANDOM (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/esp32/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_DISABLE_IRQ_ENABLE_IRQ (1) #define MICROPY_PY_MACHINE_ADC (1) diff --git a/ports/esp8266/mpconfigport.h b/ports/esp8266/mpconfigport.h index bbfab64fba..6504127755 100644 --- a/ports/esp8266/mpconfigport.h +++ b/ports/esp8266/mpconfigport.h @@ -66,6 +66,7 @@ #define MICROPY_PY_LWIP_SOCK_RAW (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/esp8266/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_DISABLE_IRQ_ENABLE_IRQ (1) #define MICROPY_PY_MACHINE_ADC (1) diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index c67b010c01..540357fa84 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -79,6 +79,7 @@ uint32_t trng_random_u32(void); #define MICROPY_PY_RANDOM_SEED_INIT_FUNC (trng_random_u32()) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/mimxrt/modmachine.c" +#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) diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index 30625382e4..37fbdf1eb3 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -174,6 +174,7 @@ #define MICROPY_PY_TIME (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/nrf/modules/machine/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_BOOTLOADER (1) #define MICROPY_PY_MACHINE_PULSE (0) diff --git a/ports/renesas-ra/mpconfigport.h b/ports/renesas-ra/mpconfigport.h index ebd055f2ee..52effd64f9 100644 --- a/ports/renesas-ra/mpconfigport.h +++ b/ports/renesas-ra/mpconfigport.h @@ -131,6 +131,7 @@ #ifndef MICROPY_PY_MACHINE #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/renesas-ra/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_BOOTLOADER (1) #define MICROPY_PY_MACHINE_ADC (1) diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 8f073002e8..c11d2fa69d 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -115,6 +115,7 @@ #define MICROPY_PY_RANDOM_SEED_INIT_FUNC (rosc_random_u32()) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/rp2/modmachine.c" +#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) diff --git a/ports/samd/mpconfigport.h b/ports/samd/mpconfigport.h index 160442a400..28a99333d3 100644 --- a/ports/samd/mpconfigport.h +++ b/ports/samd/mpconfigport.h @@ -73,6 +73,7 @@ #define MICROPY_PY_TIME_INCLUDEFILE "ports/samd/modtime.c" #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/samd/modmachine.c" +#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) diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 300ad086bf..9e1e24cf23 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -111,6 +111,7 @@ #ifndef MICROPY_PY_MACHINE #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_INCLUDEFILE "ports/stm32/modmachine.c" +#define MICROPY_PY_MACHINE_RESET (1) #define MICROPY_PY_MACHINE_BARE_METAL_FUNCS (1) #define MICROPY_PY_MACHINE_BOOTLOADER (1) #define MICROPY_PY_MACHINE_ADC (1) diff --git a/py/mpconfig.h b/py/mpconfig.h index 182115ed71..85b9e2c9cd 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1692,6 +1692,11 @@ typedef double mp_float_t; #define MICROPY_PY_MACHINE (0) #endif +// Whether to include: reset, reset_cause +#ifndef MICROPY_PY_MACHINE_RESET +#define MICROPY_PY_MACHINE_RESET (0) +#endif + // Whether to include: bitstream #ifndef MICROPY_PY_MACHINE_BITSTREAM #define MICROPY_PY_MACHINE_BITSTREAM (0) From 58a596f4a9b11a4043a96e5b90b2cff982d2a48c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Tue, 12 Mar 2024 15:11:23 +0100 Subject: [PATCH 07/49] extmod/nimble: Check for active before setting address mode. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `BLE().config(addr_mode=...)` is not safe to call if the NimBLE stack is not yet active (because it tries to acquire mutexes which should be initialized first). Signed-off-by: Daniël van de Giessen --- extmod/nimble/modbluetooth_nimble.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 92bc6764ed..8d555f1124 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -728,6 +728,9 @@ void mp_bluetooth_get_current_address(uint8_t *addr_type, uint8_t *addr) { } void mp_bluetooth_set_address_mode(uint8_t addr_mode) { + if (!mp_bluetooth_is_active()) { + mp_raise_OSError(ERRNO_BLUETOOTH_NOT_ACTIVE); + } switch (addr_mode) { case MP_BLUETOOTH_ADDRESS_MODE_PUBLIC: if (!has_public_address()) { From fff66c3069dd2f2b1152cf3412dfbcb073ea804b Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Thu, 14 Mar 2024 11:25:56 -0700 Subject: [PATCH 08/49] mimxrt/mpconfigport: Enable cryptolib and hashlib md5. Signed-off-by: Kwabena W. Agyeman --- ports/mimxrt/mpconfigport.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index 540357fa84..637c38f432 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -131,9 +131,9 @@ uint32_t trng_random_u32(void); #define MICROPY_PY_WEBSOCKET (MICROPY_PY_LWIP) #define MICROPY_PY_WEBREPL (MICROPY_PY_LWIP) #define MICROPY_PY_LWIP_SOCK_RAW (MICROPY_PY_LWIP) -// #define MICROPY_PY_HASHLIB_MD5 (MICROPY_PY_SSL) +#define MICROPY_PY_HASHLIB_MD5 (MICROPY_PY_SSL) #define MICROPY_PY_HASHLIB_SHA1 (MICROPY_PY_SSL) -// #define MICROPY_PY_CRYPTOLIB (MICROPY_PY_SSL) +#define MICROPY_PY_CRYPTOLIB (MICROPY_PY_SSL) #ifndef MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE #define MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE (1) From 47e84751fb55fb522557ca3f2349c8a25295498a Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 23 Nov 2023 09:43:06 +1100 Subject: [PATCH 09/49] py/objstr: Add a macro to define a bytes object at compile time. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- py/objstr.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/py/objstr.h b/py/objstr.h index 72fe1cfef0..028fc9597f 100644 --- a/py/objstr.h +++ b/py/objstr.h @@ -100,6 +100,8 @@ const byte *str_index_to_ptr(const mp_obj_type_t *type, const byte *self_data, s mp_obj_t index, bool is_slice); const byte *find_subbytes(const byte *haystack, size_t hlen, const byte *needle, size_t nlen, int direction); +#define MP_DEFINE_BYTES_OBJ(obj_name, target, len) mp_obj_str_t obj_name = {{&mp_type_bytes}, 0, (len), (const byte *)(target)} + mp_obj_t mp_obj_bytes_hex(size_t n_args, const mp_obj_t *args, const mp_obj_type_t *type); mp_obj_t mp_obj_bytes_fromhex(mp_obj_t type_in, mp_obj_t data); From 43904acea8b2087fdf9f283c0f8ce7d92d16e605 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 13 Mar 2024 12:17:20 +1100 Subject: [PATCH 10/49] mimxrt: Define the MICROPY_HW_ENABLE_USBDEV macro. Previously USB was always enabled, but this created some conflicts when adding guards to other files on other ports. Note the configuration with USB disabled hasn't been tested and probably won't build or run without further work. Signed-off-by: Angus Gratton --- ports/mimxrt/main.c | 2 ++ ports/mimxrt/mpconfigport.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ports/mimxrt/main.c b/ports/mimxrt/main.c index 761c491742..dddf44e605 100644 --- a/ports/mimxrt/main.c +++ b/ports/mimxrt/main.c @@ -115,7 +115,9 @@ int main(void) { // Execute user scripts. int ret = pyexec_file_if_exists("boot.py"); + #if MICROPY_HW_ENABLE_USBDEV mp_usbd_init(); + #endif if (ret & PYEXEC_FORCED_EXIT) { goto soft_reset_exit; diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index 637c38f432..a4c6d6e206 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -147,6 +147,8 @@ uint32_t trng_random_u32(void); #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-mimxrt" #endif +#define MICROPY_HW_ENABLE_USBDEV (1) + // Hooks to add builtins #if defined(IOMUX_TABLE_ENET) From 9d0d262be069089f01da6b40d2cd78f1da14de0f Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 23 Nov 2023 09:48:41 +1100 Subject: [PATCH 11/49] extmod/machine_usb_device: Add support for Python USB devices. This new machine-module driver provides a "USBDevice" singleton object and a shim TinyUSB "runtime" driver that delegates the descriptors and all of the TinyUSB callbacks to Python functions. This allows writing arbitrary USB devices in pure Python. It's also possible to have a base built-in USB device implemented in C (eg CDC, or CDC+MSC) and a Python USB device added on top of that. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/library/machine.USBDevice.rst | 286 ++++++++++++++ docs/library/machine.rst | 1 + extmod/extmod.cmake | 1 + extmod/extmod.mk | 1 + extmod/machine_usb_device.c | 335 +++++++++++++++++ extmod/modmachine.c | 3 + extmod/modmachine.h | 5 + py/runtime.c | 4 + shared/tinyusb/mp_usbd.c | 34 +- shared/tinyusb/mp_usbd.h | 101 ++++- shared/tinyusb/mp_usbd_descriptor.c | 112 +++--- shared/tinyusb/mp_usbd_internal.h | 34 -- shared/tinyusb/mp_usbd_runtime.c | 554 ++++++++++++++++++++++++++++ shared/tinyusb/tusb_config.h | 31 +- 14 files changed, 1384 insertions(+), 118 deletions(-) create mode 100644 docs/library/machine.USBDevice.rst create mode 100644 extmod/machine_usb_device.c delete mode 100644 shared/tinyusb/mp_usbd_internal.h create mode 100644 shared/tinyusb/mp_usbd_runtime.c diff --git a/docs/library/machine.USBDevice.rst b/docs/library/machine.USBDevice.rst new file mode 100644 index 0000000000..82897b280d --- /dev/null +++ b/docs/library/machine.USBDevice.rst @@ -0,0 +1,286 @@ +.. currentmodule:: machine +.. _machine.USBDevice: + +class USBDevice -- USB Device driver +==================================== + +.. note:: ``machine.USBDevice`` is currently only supported on the rp2 and samd + ports. + +USBDevice provides a low-level Python API for implementing USB device functions using +Python code. This low-level API assumes familiarity with the USB standard. It's +not recommended to use this API directly, instead install the high-level usbd +module from micropython-lib. + +.. warning:: This functionality is very new and the high-level usbd module is + not yet merged into micropython-lib. It can be found `here on + GitHub `_. + +Terminology +----------- + +- A "Runtime" USB device interface or driver is one which is defined using this + Python API after MicroPython initially starts up. + +- A "Built-in" USB device interface or driver is one that is compiled into the + MicroPython firmware, and is always available. Examples are USB-CDC (serial + port) which is usually enabled by default. Built-in USB-MSC (Mass Storage) is an + option on some ports. + +Lifecycle +--------- + +Managing a runtime USB interface can be tricky, especially if you are communicating +with MicroPython over a built-in USB-CDC serial port that's part of the same USB +device. + +- A MicroPython soft reset will always clear all runtime USB interfaces, which + results in the entire USB device disconnecting from the host. If MicroPython + is also providing a built-in USB-CDC serial port then this will re-appear + after the soft reset. + + This means some functions (like ``mpremote run``) that target the USB-CDC + serial port will immediately fail if a runtime USB interface is active, + because the port goes away when ``mpremote`` triggers a soft reset. The + operation should succeed on the second try, as after the soft reset there is + no more runtime USB interface. + +- To configure a runtime USB device on every boot, it's recommended to place the + configuration code in the ``boot.py`` file on the :ref:`device VFS + `. On each reset this file is executed before the USB subsystem is + initialised (and before ``main.py``), so it allows the board to come up with the runtime + USB device immediately. + +- For development or debugging, it may be convenient to connect a hardware + serial REPL and disable the built-in USB-CDC serial port entirely. Not all ports + support this (currently only ``rp2``). The custom build should be configured + with ``#define MICROPY_HW_USB_CDC (0)`` and ``#define + MICROPY_HW_ENABLE_UART_REPL (1)``. + +Constructors +------------ + +.. class:: USBDevice() + + Construct a USBDevice object. + + .. note:: This object is a singleton, each call to this constructor + returns the same object reference. + +Methods +------- + +.. method:: USBDevice.config(desc_dev, desc_cfg, desc_strs=None, open_itf_cb=None, reset_cb=None, control_xfer_cb=None, xfer_cb=None) + + Configures the ``USBDevice`` singleton object with the USB runtime device + state and callback functions: + + - ``desc_dev`` - A bytes-like object containing + the new USB device descriptor. + + - ``desc_cfg`` - A bytes-like object containing the + new USB configuration descriptor. + + - ``desc_strs`` - Optional object holding strings or bytes objects + containing USB string descriptor values. Can be a list, a dict, or any + object which supports subscript indexing with integer keys (USB string + descriptor index). + + Strings are an optional USB feature, and this parameter can be unset + (default) if no strings are referenced in the device and configuration + descriptors, or if only built-in strings should be used. + + Apart from index 0, all the string values should be plain ASCII. Index 0 + is the special "languages" USB descriptor, represented as a bytes object + with a custom format defined in the USB standard. ``None`` can be + returned at index 0 in order to use a default "English" language + descriptor. + + To fall back to providing a built-in string value for a given index, a + subscript lookup can return ``None``, raise ``KeyError``, or raise + ``IndexError``. + + - ``open_itf_cb`` - This callback is called once for each interface + or Interface Association Descriptor in response to a Set + Configuration request from the USB Host (the final stage before + the USB device is available to the host). + + The callback takes a single argument, which is a memoryview of the + interface or IAD descriptor that the host is accepting (including + all associated descriptors). It is a view into the same + ``desc_cfg`` object that was provided as a separate + argument to this function. The memoryview is only valid until the + callback function returns. + + - ``reset_cb`` - This callback is called when the USB host performs + a bus reset. The callback takes no arguments. Any in-progress + transfers will never complete. The USB host will most likely + proceed to re-enumerate the USB device by calling the descriptor + callbacks and then ``open_itf_cb()``. + + - ``control_xfer_cb`` - This callback is called one or more times + for each USB control transfer (device Endpoint 0). It takes two + arguments. + + The first argument is the control transfer stage. It is one of: + + - ``1`` for SETUP stage. + - ``2`` for DATA stage. + - ``3`` for ACK stage. + + Second argument is a memoryview to read the USB control request + data for this stage. The memoryview is only valid until the + callback function returns. + + The callback should return one of the following values: + + - ``False`` to stall the endpoint and reject the transfer. + - ``True`` to continue the transfer to the next stage. + - A buffer object to provide data for this stage of the transfer. + This should be a writable buffer for an ``OUT`` direction transfer, or a + readable buffer with data for an ``IN`` direction transfer. + + - ``xfer_cb`` - This callback is called whenever a non-control + transfer submitted by calling :func:`USBDevice.submit_xfer` completes. + + The callback has three arguments: + + 1. The Endpoint number for the completed transfer. + 2. Result value: ``True`` if the transfer succeeded, ``False`` + otherwise. + 3. Number of bytes successfully transferred. In the case of a + "short" transfer, The result is ``True`` and ``xferred_bytes`` + will be smaller than the length of the buffer submitted for the + transfer. + + .. note:: If a bus reset occurs (see :func:`USBDevice.reset`), + ``xfer_cb`` is not called for any transfers that have not + already completed. + +.. method:: USBDevice.active(self, [value] /) + + Returns the current active state of this runtime USB device as a + boolean. The runtime USB device is "active" when it is available to + interact with the host, it doesn't mean that a USB Host is actually + present. + + If the optional ``value`` argument is set to a truthy value, then + the USB device will be activated. + + If the optional ``value`` argument is set to a falsey value, then + the USB device is deactivated. While the USB device is deactivated, + it will not be detected by the USB Host. + + To simulate a disconnect and a reconnect of the USB device, call + ``active(False)`` followed by ``active(True)``. This may be + necessary if the runtime device configuration has changed, so that + the host sees the new device. + +.. attribute:: USDBD.builtin_driver + + This attribute holds the current built-in driver configuration, and must be + set to one of the ``USBDevice.BUILTIN_`` named constants defined on this object. + + By default it holds the value :data:`USBDevice.BUILTIN_NONE`. + + Runtime USB device must be inactive when setting this field. Call the + :func:`USBDevice.active` function to deactivate before setting if necessary + (and again to activate after setting). + + If this value is set to any value other than :data:`USBDevice.BUILTIN_NONE` then + the following restrictions apply to the :func:`USBDevice.config` arguments: + + - ``desc_cfg`` should begin with the built-in USB interface descriptor data + accessible via :data:`USBDevice.builtin_driver` attribute ``desc_cfg``. + Descriptors appended after the built-in configuration descriptors should use + interface, string and endpoint numbers starting from the max built-in values + defined in :data:`USBDevice.builtin_driver` attributes ``itf_max``, ``str_max`` and + ``ep_max``. + + - The ``bNumInterfaces`` field in the built-in configuration + descriptor will also need to be updated if any new interfaces + are appended to the end of ``desc_cfg``. + + - ``desc_strs`` should either be ``None`` or a list/dictionary where index + values less than ``USBDevice.builtin_driver.str_max`` are missing or have + value ``None``. This reserves those string indexes for the built-in + drivers. Placing a different string at any of these indexes overrides that + string in the built-in driver. + +.. method:: USBDevice.submit_xfer(self, ep, buffer /) + + Submit a USB transfer on endpoint number ``ep``. ``buffer`` must be + an object implementing the buffer interface, with read access for + ``IN`` endpoints and write access for ``OUT`` endpoints. + + .. note:: ``ep`` cannot be the control Endpoint number 0. Control + transfers are built up through successive executions of + ``control_xfer_cb``, see above. + + Returns ``True`` if successful, ``False`` if the transfer could not + be queued (as USB device is not configured by host, or because + another transfer is queued on this endpoint.) + + When the USB host completes the transfer, the ``xfer_cb`` callback + is called (see above). + + Raises ``OSError`` with reason ``MP_EINVAL`` If the USB device is not + active. + +.. method:: USBDevice.stall(self, ep, [stall] /) + + Calling this function gets or sets the STALL state of a device endpoint. + + ``ep`` is the number of the endpoint. + + If the optional ``stall`` parameter is set, this is a boolean flag + for the STALL state. + + The return value is the current stall state of the endpoint (before + any change made by this function). + + An endpoint that is set to STALL may remain stalled until this + function is called again, or STALL may be cleared automatically by + the USB host. + + Raises ``OSError`` with reason ``MP_EINVAL`` If the USB device is not + active. + +Constants +--------- + +.. data:: USBDevice.BUILTIN_NONE +.. data:: USBDevice.BUILTIN_DEFAULT +.. data:: USBDevice.BUILTIN_CDC +.. data:: USBDevice.BUILTIN_MSC +.. data:: USBDevice.BUILTIN_CDC_MSC + + These constant objects hold the built-in descriptor data which is + compiled into the MicroPython firmware. ``USBDevice.BUILTIN_NONE`` and + ``USBDevice.BUILTIN_DEFAULT`` are always present. Additional objects may be present + depending on the firmware build configuration and the actual built-in drivers. + + .. note:: Currently at most one of ``USBDevice.BUILTIN_CDC``, + ``USBDevice.BUILTIN_MSC`` and ``USBDevice.BUILTIN_CDC_MSC`` is defined + and will be the same object as ``USBDevice.BUILTIN_DEFAULT``. + These constants are defined to allow run-time detection of + the built-in driver (if any). Support for selecting one of + multiple built-in driver configurations may be added in the + future. + + These values are assigned to :data:`USBDevice.builtin_driver` to get/set the + built-in configuration. + + Each object contains the following read-only fields: + + - ``itf_max`` - One more than the highest bInterfaceNumber value used + in the built-in configuration descriptor. + - ``ep_max`` - One more than the highest bEndpointAddress value used + in the built-in configuration descriptor. Does not include any + ``IN`` flag bit (0x80). + - ``str_max`` - One more than the highest string descriptor index + value used by any built-in descriptor. + - ``desc_dev`` - ``bytes`` object containing the built-in USB device + descriptor. + - ``desc_cfg`` - ``bytes`` object containing the complete built-in USB + configuration descriptor. diff --git a/docs/library/machine.rst b/docs/library/machine.rst index 3f5cd6f13c..532266d1d9 100644 --- a/docs/library/machine.rst +++ b/docs/library/machine.rst @@ -265,3 +265,4 @@ Classes machine.WDT.rst machine.SD.rst machine.SDCard.rst + machine.USBDevice.rst diff --git a/extmod/extmod.cmake b/extmod/extmod.cmake index 53c7740132..957580d15d 100644 --- a/extmod/extmod.cmake +++ b/extmod/extmod.cmake @@ -18,6 +18,7 @@ set(MICROPY_SOURCE_EXTMOD ${MICROPY_EXTMOD_DIR}/machine_signal.c ${MICROPY_EXTMOD_DIR}/machine_spi.c ${MICROPY_EXTMOD_DIR}/machine_uart.c + ${MICROPY_EXTMOD_DIR}/machine_usb_device.c ${MICROPY_EXTMOD_DIR}/machine_wdt.c ${MICROPY_EXTMOD_DIR}/modbluetooth.c ${MICROPY_EXTMOD_DIR}/modframebuf.c diff --git a/extmod/extmod.mk b/extmod/extmod.mk index f0428bcd05..4f5f7d53ac 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -15,6 +15,7 @@ SRC_EXTMOD_C += \ extmod/machine_spi.c \ extmod/machine_timer.c \ extmod/machine_uart.c \ + extmod/machine_usb_device.c \ extmod/machine_wdt.c \ extmod/modasyncio.c \ extmod/modbinascii.c \ diff --git a/extmod/machine_usb_device.c b/extmod/machine_usb_device.c new file mode 100644 index 0000000000..69c3f38487 --- /dev/null +++ b/extmod/machine_usb_device.c @@ -0,0 +1,335 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 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. + */ + +#include "py/mpconfig.h" + +#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + +#include "mp_usbd.h" +#include "py/mperrno.h" +#include "py/objstr.h" + +// Implements the singleton runtime USB object +// +// Currently this implementation references TinyUSB directly. + +#ifndef NO_QSTR +#include "device/usbd_pvt.h" +#endif + +#define HAS_BUILTIN_DRIVERS (MICROPY_HW_USB_CDC || MICROPY_HW_USB_MSC) + +const mp_obj_type_t machine_usb_device_type; + +static mp_obj_t usb_device_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)type; + (void)n_args; + (void)n_kw; + (void)args; + + if (MP_STATE_VM(usbd) == MP_OBJ_NULL) { + mp_obj_usb_device_t *o = m_new0(mp_obj_usb_device_t, 1); + o->base.type = &machine_usb_device_type; + o->desc_dev = mp_const_none; + o->desc_cfg = mp_const_none; + o->desc_strs = mp_const_none; + o->open_itf_cb = mp_const_none; + o->reset_cb = mp_const_none; + o->control_xfer_cb = mp_const_none; + o->xfer_cb = mp_const_none; + for (int i = 0; i < CFG_TUD_ENDPPOINT_MAX; i++) { + o->xfer_data[i][0] = mp_const_none; + o->xfer_data[i][1] = mp_const_none; + } + o->builtin_driver = MP_OBJ_FROM_PTR(&mp_type_usb_device_builtin_none); + o->active = false; // Builtin USB may be active already, but runtime is inactive + o->trigger = false; + o->control_data = MP_OBJ_TO_PTR(mp_obj_new_memoryview('B', 0, NULL)); + o->num_pend_excs = 0; + for (int i = 0; i < MP_USBD_MAX_PEND_EXCS; i++) { + o->pend_excs[i] = mp_const_none; + } + + MP_STATE_VM(usbd) = MP_OBJ_FROM_PTR(o); + } + + return MP_STATE_VM(usbd); +} + +// Utility helper to raise an error if USB device is not active +// (or if a change of active state is triggered but not processed.) +static void usb_device_check_active(mp_obj_usb_device_t *usbd) { + if (!usbd->active || usbd->trigger) { + mp_raise_OSError(MP_EINVAL); + } +} + +static mp_obj_t usb_device_submit_xfer(mp_obj_t self, mp_obj_t ep, mp_obj_t buffer) { + mp_obj_usb_device_t *usbd = (mp_obj_usb_device_t *)MP_OBJ_TO_PTR(self); + int ep_addr; + mp_buffer_info_t buf_info = { 0 }; + bool result; + + usb_device_check_active(usbd); + + // Unmarshal arguments, raises TypeError if invalid + ep_addr = mp_obj_get_int(ep); + mp_get_buffer_raise(buffer, &buf_info, ep_addr & TUSB_DIR_IN_MASK ? MP_BUFFER_READ : MP_BUFFER_RW); + + uint8_t ep_num = tu_edpt_number(ep_addr); + uint8_t ep_dir = tu_edpt_dir(ep_addr); + + if (ep_num == 0 || ep_num >= CFG_TUD_ENDPPOINT_MAX) { + // TinyUSB usbd API doesn't range check arguments, so this check avoids + // out of bounds array access, or submitting transfers on the control endpoint. + // + // This C layer doesn't otherwise keep track of which endpoints the host + // is aware of (or not). + mp_raise_ValueError("ep"); + } + + if (!usbd_edpt_claim(USBD_RHPORT, ep_addr)) { + mp_raise_OSError(MP_EBUSY); + } + + result = usbd_edpt_xfer(USBD_RHPORT, ep_addr, buf_info.buf, buf_info.len); + + if (result) { + // Store the buffer object until the transfer completes + usbd->xfer_data[ep_num][ep_dir] = buffer; + } + + return mp_obj_new_bool(result); +} +static MP_DEFINE_CONST_FUN_OBJ_3(usb_device_submit_xfer_obj, usb_device_submit_xfer); + +static mp_obj_t usb_device_active(size_t n_args, const mp_obj_t *args) { + mp_obj_usb_device_t *usbd = (mp_obj_usb_device_t *)MP_OBJ_TO_PTR(args[0]); + + bool result = usbd->active; + if (n_args == 2) { + bool value = mp_obj_is_true(args[1]); + + if (value != result) { + if (value + && !mp_usb_device_builtin_enabled(usbd) + && usbd->desc_dev == mp_const_none) { + // Only allow activating if config() has already been called to set some descriptors, or a + // built-in driver is enabled + mp_raise_OSError(MP_EINVAL); + } + + // Any change to active state is triggered here and processed + // from the TinyUSB task. + usbd->active = value; + usbd->trigger = true; + if (value) { + mp_usbd_init(); // Ensure TinyUSB has initialised by this point + } + mp_usbd_schedule_task(); + } + } + + return mp_obj_new_bool(result); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(usb_device_active_obj, 1, 2, usb_device_active); + +static mp_obj_t usb_device_stall(size_t n_args, const mp_obj_t *args) { + mp_obj_usb_device_t *self = (mp_obj_usb_device_t *)MP_OBJ_TO_PTR(args[0]); + int epnum = mp_obj_get_int(args[1]); + + usb_device_check_active(self); + + mp_obj_t res = mp_obj_new_bool(usbd_edpt_stalled(USBD_RHPORT, epnum)); + + if (n_args == 3) { // Set stall state + mp_obj_t stall = args[2]; + if (mp_obj_is_true(stall)) { + usbd_edpt_stall(USBD_RHPORT, epnum); + } else { + usbd_edpt_clear_stall(USBD_RHPORT, epnum); + } + } + + return res; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(usb_device_stall_obj, 2, 3, usb_device_stall); + +// Configure the singleton USB device with all of the relevant transfer and descriptor +// callbacks for dynamic devices. +static mp_obj_t usb_device_config(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + mp_obj_usb_device_t *self = (mp_obj_usb_device_t *)MP_OBJ_TO_PTR(pos_args[0]); + + enum { ARG_desc_dev, ARG_desc_cfg, ARG_desc_strs, ARG_open_itf_cb, + ARG_reset_cb, ARG_control_xfer_cb, ARG_xfer_cb, ARG_active }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_desc_dev, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_desc_cfg, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_desc_strs, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_open_itf_cb, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_reset_cb, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_control_xfer_cb, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_xfer_cb, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Check descriptor arguments + mp_obj_t desc_dev = args[ARG_desc_dev].u_obj; + mp_obj_t desc_cfg = args[ARG_desc_cfg].u_obj; + mp_obj_t desc_strs = args[ARG_desc_strs].u_obj; + if (!MP_OBJ_TYPE_HAS_SLOT(mp_obj_get_type(desc_dev), buffer)) { + mp_raise_ValueError(MP_ERROR_TEXT("desc_dev")); + } + if (!MP_OBJ_TYPE_HAS_SLOT(mp_obj_get_type(desc_cfg), buffer)) { + mp_raise_ValueError(MP_ERROR_TEXT("desc_cfg")); + } + if (desc_strs != mp_const_none + && !MP_OBJ_TYPE_HAS_SLOT(mp_obj_get_type(desc_strs), subscr)) { + mp_raise_ValueError(MP_ERROR_TEXT("desc_strs")); + } + + self->desc_dev = desc_dev; + self->desc_cfg = desc_cfg; + self->desc_strs = desc_strs; + self->open_itf_cb = args[ARG_open_itf_cb].u_obj; + self->reset_cb = args[ARG_reset_cb].u_obj; + self->control_xfer_cb = args[ARG_control_xfer_cb].u_obj; + self->xfer_cb = args[ARG_xfer_cb].u_obj; + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(usb_device_config_obj, 1, usb_device_config); + +static const MP_DEFINE_BYTES_OBJ(builtin_default_desc_dev_obj, + &mp_usbd_builtin_desc_dev, sizeof(tusb_desc_device_t)); + +#if HAS_BUILTIN_DRIVERS +// BUILTIN_DEFAULT Python object holds properties of the built-in USB configuration +// (i.e. values used by the C implementation of TinyUSB devices.) +static const MP_DEFINE_BYTES_OBJ(builtin_default_desc_cfg_obj, + mp_usbd_builtin_desc_cfg, MP_USBD_BUILTIN_DESC_CFG_LEN); + +static const mp_rom_map_elem_t usb_device_builtin_default_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_itf_max), MP_OBJ_NEW_SMALL_INT(USBD_ITF_BUILTIN_MAX) }, + { MP_ROM_QSTR(MP_QSTR_ep_max), MP_OBJ_NEW_SMALL_INT(USBD_EP_BUILTIN_MAX) }, + { MP_ROM_QSTR(MP_QSTR_str_max), MP_OBJ_NEW_SMALL_INT(USBD_STR_BUILTIN_MAX) }, + { MP_ROM_QSTR(MP_QSTR_desc_dev), MP_ROM_PTR(&builtin_default_desc_dev_obj) }, + { MP_ROM_QSTR(MP_QSTR_desc_cfg), MP_ROM_PTR(&builtin_default_desc_cfg_obj) }, +}; +static MP_DEFINE_CONST_DICT(usb_device_builtin_default_dict, usb_device_builtin_default_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + mp_type_usb_device_builtin_default, + MP_QSTR_BUILTIN_DEFAULT, + MP_TYPE_FLAG_NONE, + locals_dict, &usb_device_builtin_default_dict + ); +#endif // HAS_BUILTIN_DRIVERS + +// BUILTIN_NONE holds properties for no enabled built-in USB device support +static const mp_rom_map_elem_t usb_device_builtin_none_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_itf_max), MP_OBJ_NEW_SMALL_INT(0) }, + { MP_ROM_QSTR(MP_QSTR_ep_max), MP_OBJ_NEW_SMALL_INT(0) }, + { MP_ROM_QSTR(MP_QSTR_str_max), MP_OBJ_NEW_SMALL_INT(1) }, + { MP_ROM_QSTR(MP_QSTR_desc_dev), MP_ROM_PTR(&builtin_default_desc_dev_obj) }, + { MP_ROM_QSTR(MP_QSTR_desc_cfg), mp_const_empty_bytes }, +}; +static MP_DEFINE_CONST_DICT(usb_device_builtin_none_dict, usb_device_builtin_none_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + mp_type_usb_device_builtin_none, + MP_QSTR_BUILTIN_NONE, + MP_TYPE_FLAG_NONE, + locals_dict, &usb_device_builtin_none_dict + ); + +static const mp_rom_map_elem_t usb_device_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&usb_device_config_obj) }, + { MP_ROM_QSTR(MP_QSTR_submit_xfer), MP_ROM_PTR(&usb_device_submit_xfer_obj) }, + { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&usb_device_active_obj) }, + { MP_ROM_QSTR(MP_QSTR_stall), MP_ROM_PTR(&usb_device_stall_obj) }, + + // Built-in driver constants + { MP_ROM_QSTR(MP_QSTR_BUILTIN_NONE), MP_ROM_PTR(&mp_type_usb_device_builtin_none) }, + + #if !HAS_BUILTIN_DRIVERS + // No builtin-in drivers, so BUILTIN_DEFAULT is BUILTIN_NONE + { MP_ROM_QSTR(MP_QSTR_BUILTIN_DEFAULT), MP_ROM_PTR(&mp_type_usb_device_builtin_none) }, + #else + { MP_ROM_QSTR(MP_QSTR_BUILTIN_DEFAULT), MP_ROM_PTR(&mp_type_usb_device_builtin_default) }, + + // Specific driver constant names are to support future switching of built-in drivers, + // but currently only one is present and it maps directly to BUILTIN_DEFAULT + #if MICROPY_HW_USB_CDC && !MICROPY_HW_USB_MSC + { MP_ROM_QSTR(MP_QSTR_BUILTIN_CDC), MP_ROM_PTR(&mp_type_usb_device_builtin_default) }, + #endif + #if MICROPY_HW_USB_MSC && !MICROPY_HW_USB_CDC + { MP_ROM_QSTR(MP_QSTR_BUILTIN_MSC), MP_ROM_PTR(&mp_type_usb_device_builtin_default) }, + #endif + #if MICROPY_HW_USB_CDC && MICROPY_HW_USB_MSC + { MP_ROM_QSTR(MP_QSTR_BUILTIN_CDC_MSC), MP_ROM_PTR(&mp_type_usb_device_builtin_default) }, + #endif + #endif // !HAS_BUILTIN_DRIVERS +}; +static MP_DEFINE_CONST_DICT(usb_device_locals_dict, usb_device_locals_dict_table); + +static void usb_device_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + mp_obj_usb_device_t *self = MP_OBJ_TO_PTR(self_in); + if (dest[0] == MP_OBJ_NULL) { + // Load attribute. + if (attr == MP_QSTR_builtin_driver) { + dest[0] = self->builtin_driver; + } else { + // Continue lookup in locals_dict. + dest[1] = MP_OBJ_SENTINEL; + } + } else if (dest[1] != MP_OBJ_NULL) { + // Store attribute. + if (attr == MP_QSTR_builtin_driver) { + if (self->active) { + mp_raise_OSError(MP_EINVAL); // Need to deactivate first + } + // Note: this value should be one of the BUILTIN_nnn constants, + // but not checked here to save code size in a low level API + self->builtin_driver = dest[1]; + dest[0] = MP_OBJ_NULL; + } + } +} + +MP_DEFINE_CONST_OBJ_TYPE( + machine_usb_device_type, + MP_QSTR_USBDevice, + MP_TYPE_FLAG_NONE, + make_new, usb_device_make_new, + locals_dict, &usb_device_locals_dict, + attr, &usb_device_attr + ); + +MP_REGISTER_ROOT_POINTER(mp_obj_t usbd); + +#endif diff --git a/extmod/modmachine.c b/extmod/modmachine.c index d7e82b124d..2a7e315bbb 100644 --- a/extmod/modmachine.c +++ b/extmod/modmachine.c @@ -232,6 +232,9 @@ static const mp_rom_map_elem_t machine_module_globals_table[] = { #if MICROPY_PY_MACHINE_UART { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&machine_uart_type) }, #endif + #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + { MP_ROM_QSTR(MP_QSTR_USBDevice), MP_ROM_PTR(&machine_usb_device_type) }, + #endif #if MICROPY_PY_MACHINE_WDT { MP_ROM_QSTR(MP_QSTR_WDT), MP_ROM_PTR(&machine_wdt_type) }, #endif diff --git a/extmod/modmachine.h b/extmod/modmachine.h index e6b08b3fc9..7c16ed302e 100644 --- a/extmod/modmachine.h +++ b/extmod/modmachine.h @@ -213,6 +213,7 @@ extern const mp_obj_type_t machine_signal_type; extern const mp_obj_type_t machine_spi_type; extern const mp_obj_type_t machine_timer_type; extern const mp_obj_type_t machine_uart_type; +extern const mp_obj_type_t machine_usbd_type; extern const mp_obj_type_t machine_wdt_type; #if MICROPY_PY_MACHINE_SOFTI2C @@ -230,6 +231,10 @@ extern const mp_machine_spi_p_t mp_machine_soft_spi_p; extern const mp_obj_dict_t mp_machine_spi_locals_dict; #endif +#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE +extern const mp_obj_type_t machine_usb_device_type; +#endif + #if defined(MICROPY_MACHINE_MEM_GET_READ_ADDR) uintptr_t MICROPY_MACHINE_MEM_GET_READ_ADDR(mp_obj_t addr_o, uint align); #endif diff --git a/py/runtime.c b/py/runtime.c index f7e0abdb46..1836f5d92a 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -171,6 +171,10 @@ void mp_init(void) { MP_STATE_VM(bluetooth) = MP_OBJ_NULL; #endif + #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + MP_STATE_VM(usbd) = MP_OBJ_NULL; + #endif + #if MICROPY_PY_THREAD_GIL mp_thread_mutex_init(&MP_STATE_VM(gil_mutex)); #endif diff --git a/shared/tinyusb/mp_usbd.c b/shared/tinyusb/mp_usbd.c index 74b3f07492..436314dcde 100644 --- a/shared/tinyusb/mp_usbd.c +++ b/shared/tinyusb/mp_usbd.c @@ -24,42 +24,42 @@ * THE SOFTWARE. */ -#include - #include "py/mpconfig.h" -#include "py/runtime.h" #if MICROPY_HW_ENABLE_USBDEV +#include "mp_usbd.h" + #ifndef NO_QSTR -#include "tusb.h" // TinyUSB is not available when running the string preprocessor #include "device/dcd.h" -#include "device/usbd.h" -#include "device/usbd_pvt.h" #endif -// TinyUSB task function wrapper, as scheduled from the USB IRQ -static void mp_usbd_task_callback(mp_sched_node_t *node); - -extern void __real_dcd_event_handler(dcd_event_t const *event, bool in_isr); +#if !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE void mp_usbd_task(void) { tud_task_ext(0, false); } +void mp_usbd_task_callback(mp_sched_node_t *node) { + (void)node; + mp_usbd_task(); +} + +#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) { - static mp_sched_node_t usbd_task_node; - __real_dcd_event_handler(event, in_isr); - mp_sched_schedule_node(&usbd_task_node, mp_usbd_task_callback); + mp_usbd_schedule_task(); } -static void mp_usbd_task_callback(mp_sched_node_t *node) { - (void)node; - mp_usbd_task(); +TU_ATTR_FAST_FUNC void mp_usbd_schedule_task(void) { + static mp_sched_node_t usbd_task_node; + mp_sched_schedule_node(&usbd_task_node, mp_usbd_task_callback); } void mp_usbd_hex_str(char *out_str, const uint8_t *bytes, size_t bytes_len) { @@ -72,4 +72,4 @@ void mp_usbd_hex_str(char *out_str, const uint8_t *bytes, size_t bytes_len) { out_str[hex_len] = 0; } -#endif +#endif // MICROPY_HW_ENABLE_USBDEV diff --git a/shared/tinyusb/mp_usbd.h b/shared/tinyusb/mp_usbd.h index 89f8bf0ee9..ef32348451 100644 --- a/shared/tinyusb/mp_usbd.h +++ b/shared/tinyusb/mp_usbd.h @@ -27,25 +27,110 @@ #ifndef MICROPY_INCLUDED_SHARED_TINYUSB_MP_USBD_H #define MICROPY_INCLUDED_SHARED_TINYUSB_MP_USBD_H +#include "py/mpconfig.h" + +#if MICROPY_HW_ENABLE_USBDEV + #include "py/obj.h" +#include "py/objarray.h" +#include "py/runtime.h" + +#ifndef NO_QSTR #include "tusb.h" +#include "device/dcd.h" +#endif -static inline void mp_usbd_init(void) { - // Currently this is a thin wrapper around tusb_init(), however - // runtime USB support will require this to be extended. - tusb_init(); -} - -// Call this to explicitly run the TinyUSB device task. +// Run the TinyUSB device task void mp_usbd_task(void); +// Schedule a call to mp_usbd_task(), even if no USB interrupt has occurred +void mp_usbd_schedule_task(void); + // Function to be implemented in port code. // Can write a string up to MICROPY_HW_USB_DESC_STR_MAX characters long, plus terminating byte. extern void mp_usbd_port_get_serial_number(char *buf); -// Most ports need to write a hexadecimal serial number from a byte array, this +// Most ports need to write a hexadecimal serial number from a byte array. This // is a helper function for this. out_str must be long enough to hold a string of total // length (2 * bytes_len + 1) (including NUL terminator). void mp_usbd_hex_str(char *out_str, const uint8_t *bytes, size_t bytes_len); +// Length of built-in configuration descriptor +#define MP_USBD_BUILTIN_DESC_CFG_LEN (TUD_CONFIG_DESC_LEN + \ + (CFG_TUD_CDC ? (TUD_CDC_DESC_LEN) : 0) + \ + (CFG_TUD_MSC ? (TUD_MSC_DESC_LEN) : 0) \ + ) + +// Built-in USB device and configuration descriptor values +extern const tusb_desc_device_t mp_usbd_builtin_desc_dev; +extern const uint8_t mp_usbd_builtin_desc_cfg[MP_USBD_BUILTIN_DESC_CFG_LEN]; + +void mp_usbd_task_callback(mp_sched_node_t *node); + +#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE +void mp_usbd_deinit(void); +void mp_usbd_init(void); + +const char *mp_usbd_runtime_string_cb(uint8_t index); + +// Maximum number of pending exceptions per single TinyUSB task execution +#define MP_USBD_MAX_PEND_EXCS 2 + +typedef struct { + mp_obj_base_t base; + + mp_obj_t desc_dev; // Device descriptor bytes + mp_obj_t desc_cfg; // Configuration descriptor bytes + mp_obj_t desc_strs; // List/dict/similar to look up string descriptors by index + + // Runtime device driver callback functions + mp_obj_t open_itf_cb; + mp_obj_t reset_cb; + mp_obj_t control_xfer_cb; + mp_obj_t xfer_cb; + + mp_obj_t builtin_driver; // Points to one of mp_type_usb_device_builtin_nnn + + bool active; // Has the user set the USB device active? + bool trigger; // Has the user requested the active state change (or re-activate)? + + // Temporary pointers for xfer data in progress on each endpoint + // Ensuring they aren't garbage collected until the xfer completes + mp_obj_t xfer_data[CFG_TUD_ENDPPOINT_MAX][2]; + + // Pointer to a memoryview that is reused to refer to various pieces of + // control transfer data that are pushed to USB control transfer + // callbacks. Python code can't rely on the memoryview contents + // to remain valid after the callback returns! + mp_obj_array_t *control_data; + + // Pointers to exceptions thrown inside Python callbacks. See + // usbd_callback_function_n(). + mp_uint_t num_pend_excs; + mp_obj_t pend_excs[MP_USBD_MAX_PEND_EXCS]; +} mp_obj_usb_device_t; + +// Built-in constant objects, possible values of builtin_driver +// +// (Currently not possible to change built-in drivers at runtime, just enable/disable.) +extern const mp_obj_type_t mp_type_usb_device_builtin_default; +extern const mp_obj_type_t mp_type_usb_device_builtin_none; + +// Return true if any built-in driver is enabled +inline static bool mp_usb_device_builtin_enabled(const mp_obj_usb_device_t *usbd) { + return usbd->builtin_driver != MP_OBJ_FROM_PTR(&mp_type_usb_device_builtin_none); +} + +#else // Static USBD drivers only + +static inline void mp_usbd_init(void) { + // Without runtime USB support, this can be a thin wrapper wrapper around tusb_init() + extern bool tusb_init(void); + tusb_init(); +} + +#endif + +#endif // MICROPY_HW_ENABLE_USBDEV + #endif // MICROPY_INCLUDED_SHARED_TINYUSB_USBD_H diff --git a/shared/tinyusb/mp_usbd_descriptor.c b/shared/tinyusb/mp_usbd_descriptor.c index 72a2652179..be3473b6b9 100644 --- a/shared/tinyusb/mp_usbd_descriptor.c +++ b/shared/tinyusb/mp_usbd_descriptor.c @@ -31,12 +31,11 @@ #include "tusb.h" #include "mp_usbd.h" -#include "mp_usbd_internal.h" #define USBD_CDC_CMD_MAX_SIZE (8) #define USBD_CDC_IN_OUT_MAX_SIZE ((CFG_TUD_MAX_SPEED == OPT_MODE_HIGH_SPEED) ? 512 : 64) -const tusb_desc_device_t mp_usbd_desc_device_static = { +const tusb_desc_device_t mp_usbd_builtin_desc_dev = { .bLength = sizeof(tusb_desc_device_t), .bDescriptorType = TUSB_DESC_DEVICE, .bcdUSB = 0x0200, @@ -53,8 +52,8 @@ const tusb_desc_device_t mp_usbd_desc_device_static = { .bNumConfigurations = 1, }; -const uint8_t mp_usbd_desc_cfg_static[USBD_STATIC_DESC_LEN] = { - TUD_CONFIG_DESCRIPTOR(1, USBD_ITF_STATIC_MAX, USBD_STR_0, USBD_STATIC_DESC_LEN, +const uint8_t mp_usbd_builtin_desc_cfg[MP_USBD_BUILTIN_DESC_CFG_LEN] = { + TUD_CONFIG_DESCRIPTOR(1, USBD_ITF_BUILTIN_MAX, USBD_STR_0, MP_USBD_BUILTIN_DESC_CFG_LEN, 0, USBD_MAX_POWER_MA), #if CFG_TUD_CDC @@ -69,51 +68,68 @@ const uint8_t mp_usbd_desc_cfg_static[USBD_STATIC_DESC_LEN] = { const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { char serial_buf[MICROPY_HW_USB_DESC_STR_MAX + 1]; // Includes terminating NUL byte static uint16_t desc_wstr[MICROPY_HW_USB_DESC_STR_MAX + 1]; // Includes prefix uint16_t - const char *desc_str; + const char *desc_str = NULL; uint16_t desc_len; - switch (index) { - case 0: - desc_wstr[1] = 0x0409; // supported language is English - desc_len = 4; - break; - case USBD_STR_SERIAL: - // TODO: make a port-specific serial number callback - mp_usbd_port_get_serial_number(serial_buf); - desc_str = serial_buf; - break; - case USBD_STR_MANUF: - desc_str = MICROPY_HW_USB_MANUFACTURER_STRING; - break; - case USBD_STR_PRODUCT: - desc_str = MICROPY_HW_USB_PRODUCT_FS_STRING; - break; - #if CFG_TUD_CDC - case USBD_STR_CDC: - desc_str = MICROPY_HW_USB_CDC_INTERFACE_STRING; - break; - #endif - #if CFG_TUD_MSC - case USBD_STR_MSC: - desc_str = MICROPY_HW_USB_MSC_INTERFACE_STRING; - break; - #endif - default: - desc_str = NULL; - } + #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + desc_str = mp_usbd_runtime_string_cb(index); + #endif - if (index != 0) { + if (index == 0) { + // String descriptor 0 is special, see USB 2.0 section 9.6.7 String + // + // Expect any runtime value in desc_str to be a fully formed descriptor if (desc_str == NULL) { - return NULL; // Will STALL the endpoint + desc_str = "\x04\x03\x09\x04"; // Descriptor for "English" } + if (desc_str[0] < sizeof(desc_wstr)) { + memcpy(desc_wstr, desc_str, desc_str[0]); + return desc_wstr; + } + return NULL; // Descriptor length too long (or malformed), stall endpoint + } - // Convert from narrow string to wide string - desc_len = 2; - for (int i = 0; i < MICROPY_HW_USB_DESC_STR_MAX && desc_str[i] != 0; i++) { - desc_wstr[1 + i] = desc_str[i]; - desc_len += 2; + // Otherwise, generate a "UNICODE" string descriptor from the C string + + if (desc_str == NULL) { + // Fall back to the "static" string + switch (index) { + case USBD_STR_SERIAL: + mp_usbd_port_get_serial_number(serial_buf); + desc_str = serial_buf; + break; + case USBD_STR_MANUF: + desc_str = MICROPY_HW_USB_MANUFACTURER_STRING; + break; + case USBD_STR_PRODUCT: + desc_str = MICROPY_HW_USB_PRODUCT_FS_STRING; + break; + #if CFG_TUD_CDC + case USBD_STR_CDC: + desc_str = MICROPY_HW_USB_CDC_INTERFACE_STRING; + break; + #endif + #if CFG_TUD_MSC + case USBD_STR_MSC: + desc_str = MICROPY_HW_USB_MSC_INTERFACE_STRING; + break; + #endif + default: + break; } } + + if (desc_str == NULL) { + return NULL; // No string, STALL the endpoint + } + + // Convert from narrow string to wide string + desc_len = 2; + for (int i = 0; i < MICROPY_HW_USB_DESC_STR_MAX && desc_str[i] != 0; i++) { + desc_wstr[1 + i] = desc_str[i]; + desc_len += 2; + } + // first byte is length (including header), second byte is string type desc_wstr[0] = (TUSB_DESC_STRING << 8) | desc_len; @@ -121,13 +137,21 @@ const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { } +#if !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + const uint8_t *tud_descriptor_device_cb(void) { - return (const void *)&mp_usbd_desc_device_static; + return (const void *)&mp_usbd_builtin_desc_dev; } const uint8_t *tud_descriptor_configuration_cb(uint8_t index) { (void)index; - return mp_usbd_desc_cfg_static; + return mp_usbd_builtin_desc_cfg; } -#endif +#else + +// If runtime device support is enabled, descriptor callbacks are implemented in usbd.c + +#endif // !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + +#endif // MICROPY_HW_ENABLE_USBDEV diff --git a/shared/tinyusb/mp_usbd_internal.h b/shared/tinyusb/mp_usbd_internal.h deleted file mode 100644 index 8b8f50bae8..0000000000 --- a/shared/tinyusb/mp_usbd_internal.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2022 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_SHARED_TINYUSB_MP_USBD_INTERNAL_H -#define MICROPY_INCLUDED_SHARED_TINYUSB_MP_USBD_INTERNAL_H -#include "tusb.h" - -// Static USB device descriptor values -extern const tusb_desc_device_t mp_usbd_desc_device_static; -extern const uint8_t mp_usbd_desc_cfg_static[USBD_STATIC_DESC_LEN]; - -#endif // MICROPY_INCLUDED_SHARED_TINYUSB_MP_USBD_INTERNAL_H diff --git a/shared/tinyusb/mp_usbd_runtime.c b/shared/tinyusb/mp_usbd_runtime.c new file mode 100644 index 0000000000..a46c0f85e6 --- /dev/null +++ b/shared/tinyusb/mp_usbd_runtime.c @@ -0,0 +1,554 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Blake W. Felt + * Copyright (c) 2022-2023 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. + */ +#include + +#include "mp_usbd.h" +#include "py/mpconfig.h" +#include "py/mperrno.h" +#include "py/mphal.h" +#include "py/obj.h" +#include "py/objarray.h" +#include "py/objstr.h" +#include "py/runtime.h" + +#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + +#ifndef NO_QSTR +#include "tusb.h" // TinyUSB is not available when running the string preprocessor +#include "device/dcd.h" +#include "device/usbd.h" +#include "device/usbd_pvt.h" +#endif + +static bool in_usbd_task; // Flags if mp_usbd_task() is processing already + +// Some top-level functions that manage global TinyUSB USBD state, not the +// singleton object visible to Python +static void mp_usbd_disconnect(mp_obj_usb_device_t *usbd); +static void mp_usbd_task_inner(void); + +// Pend an exception raise in a USBD callback to print when safe. +// +// We can't raise any exceptions out of the TinyUSB task, as it may still need +// to do some state cleanup. +// +// The requirement for this becomes very similar to +// mp_call_function_x_protected() for interrupts, but it's more restrictive: if +// the C-based USB-CDC serial port is in use, we can't print from inside a +// TinyUSB callback as it might try to recursively call into TinyUSB to flush +// the CDC port and make room. Therefore, we have to store the exception and +// print it as we exit the TinyUSB task. +// +// (Worse, a single TinyUSB task can process multiple callbacks and therefore generate +// multiple exceptions...) +static void usbd_pend_exception(mp_obj_t exception) { + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + assert(usbd != NULL); + if (usbd->num_pend_excs < MP_USBD_MAX_PEND_EXCS) { + usbd->pend_excs[usbd->num_pend_excs] = exception; + } + usbd->num_pend_excs++; +} + +// Call a Python function from inside a TinyUSB callback. +// +// Handles any exception using usbd_pend_exception() +static mp_obj_t usbd_callback_function_n(mp_obj_t fun, size_t n_args, const mp_obj_t *args) { + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_obj_t ret = mp_call_function_n_kw(fun, n_args, 0, args); + nlr_pop(); + return ret; + } else { + usbd_pend_exception(MP_OBJ_FROM_PTR(nlr.ret_val)); + return MP_OBJ_NULL; + } +} + +// Return a pointer to the data inside a Python buffer provided in a callback +static void *usbd_get_buffer_in_cb(mp_obj_t obj, mp_uint_t flags) { + mp_buffer_info_t buf_info; + if (obj == mp_const_none) { + // This is only if the user somehow + return NULL; + } else if (mp_get_buffer(obj, &buf_info, flags)) { + return buf_info.buf; + } else { + mp_obj_t exc = mp_obj_new_exception_msg(&mp_type_TypeError, + MP_ERROR_TEXT("object with buffer protocol required")); + usbd_pend_exception(exc); + return NULL; + } +} + +const uint8_t *tud_descriptor_device_cb(void) { + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + const void *result = NULL; + if (usbd) { + result = usbd_get_buffer_in_cb(usbd->desc_dev, MP_BUFFER_READ); + } + return result ? result : &mp_usbd_builtin_desc_dev; +} + +const uint8_t *tud_descriptor_configuration_cb(uint8_t index) { + (void)index; + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + const void *result = NULL; + if (usbd) { + result = usbd_get_buffer_in_cb(usbd->desc_cfg, MP_BUFFER_READ); + } + return result ? result : &mp_usbd_builtin_desc_cfg; +} + +const char *mp_usbd_runtime_string_cb(uint8_t index) { + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + nlr_buf_t nlr; + + if (usbd == NULL || usbd->desc_strs == mp_const_none) { + return NULL; + } + + if (nlr_push(&nlr) == 0) { + mp_obj_t res = mp_obj_subscr(usbd->desc_strs, mp_obj_new_int(index), MP_OBJ_SENTINEL); + nlr_pop(); + if (res != mp_const_none) { + return usbd_get_buffer_in_cb(res, MP_BUFFER_READ); + } + } else { + mp_obj_t exception = MP_OBJ_FROM_PTR(nlr.ret_val); + if (!(mp_obj_is_type(exception, &mp_type_KeyError) || mp_obj_is_type(exception, &mp_type_IndexError))) { + // Don't print KeyError or IndexError, allowing dicts or lists to have missing entries. + // but log any more exotic errors that pop up + usbd_pend_exception(exception); + } + } + + return NULL; +} + +bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { + return false; // Currently no support for Vendor control transfers on the Python side +} + +// Generic "runtime device" TinyUSB class driver, delegates everything to Python callbacks + +static void runtime_dev_init(void) { +} + +static void runtime_dev_reset(uint8_t rhport) { + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + if (!usbd) { + return; + } + + for (int epnum = 0; epnum < CFG_TUD_ENDPPOINT_MAX; epnum++) { + for (int dir = 0; dir < 2; dir++) { + usbd->xfer_data[epnum][dir] = mp_const_none; + } + } + + if (mp_obj_is_callable(usbd->reset_cb)) { + usbd_callback_function_n(usbd->reset_cb, 0, NULL); + } +} + +// Calculate how many interfaces TinyUSB expects us to claim from +// driver open(). +// +// Annoyingly, the calling function (process_set_config() in TinyUSB) knows +// this but doesn't pass the information to us. +// +// The answer is: +// - If an Interface Association Descriptor (IAD) is immediately before itf_desc +// in the configuration descriptor, then claim all of the associated interfaces. +// - Otherwise, claim exactly one interface +// +// Relying on the implementation detail that itf_desc is a pointer inside the +// tud_descriptor_configuration_cb() result. Therefore, we can iterate through +// from the beginning to check for an IAD immediately preceding it. +// +// Returns the number of associated interfaces to claim. +static uint8_t _runtime_dev_count_itfs(tusb_desc_interface_t const *itf_desc) { + const tusb_desc_configuration_t *cfg_desc = (const void *)tud_descriptor_configuration_cb(0); + const uint8_t *p_desc = (const void *)cfg_desc; + const uint8_t *p_end = p_desc + cfg_desc->wTotalLength; + assert(p_desc <= itf_desc && itf_desc < p_end); + while (p_desc != (const void *)itf_desc && p_desc < p_end) { + const uint8_t *next = tu_desc_next(p_desc); + + if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE_ASSOCIATION + && next == (const void *)itf_desc) { + const tusb_desc_interface_assoc_t *desc_iad = (const void *)p_desc; + return desc_iad->bInterfaceCount; + } + p_desc = next; + } + return 1; // No IAD found +} + +// Scan the other descriptors after these interface(s) to find the total associated length to claim +// from driver open(). +// +// Total number of interfaces to scan for is assoc_itf_count. +// +// Opens any associated endpoints so they can have transfers submitted against them. +// +// Returns the total number of descriptor bytes to claim. +static uint16_t _runtime_dev_claim_itfs(tusb_desc_interface_t const *itf_desc, uint8_t assoc_itf_count, uint16_t max_len) { + const uint8_t *p_desc = (const void *)itf_desc; + const uint8_t *p_end = p_desc + max_len; + while (p_desc < p_end) { + if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE) { + if (assoc_itf_count > 0) { + // Claim this interface descriptor + assoc_itf_count--; + } else { + // This is the end of the previous interface's associated descriptors + break; + } + } else if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) { + // Open any endpoints that we come across + if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) { + bool r = usbd_edpt_open(USBD_RHPORT, (const void *)p_desc); + if (!r) { + mp_obj_t exc = mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(MP_ENODEV)); + usbd_pend_exception(exc); + break; + } + } + } + p_desc = tu_desc_next(p_desc); + } + return p_desc - (const uint8_t *)itf_desc; +} + +// TinyUSB "Application driver" open callback. Called when the USB host sets +// configuration. Returns number of bytes to claim from descriptors pointed to +// by itf_desc. +// +// This is a little fiddly as it's called before any compiled-in "static" +// TinyUSB drivers, but we don't want to override those. +// +// Also, TinyUSB expects us to know how many interfaces to claim for each time +// this function is called, and will behave unexpectedly if we claim the wrong +// number of interfaces. However, unlike a "normal" USB driver we don't know at +// compile time how many interfaces we've implemented. Instead, we have to look +// back through the configuration descriptor to figure this out. +static uint16_t runtime_dev_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + + // Runtime USB isn't initialised + if (!usbd) { + return 0; + } + + // If TinyUSB built-in drivers are enabled, don't claim any interface in the static range + if (mp_usb_device_builtin_enabled(usbd) && itf_desc->bInterfaceNumber < USBD_ITF_BUILTIN_MAX) { + return 0; + } + + // Determine the total descriptor length of the interface(s) we are going to claim + uint8_t assoc_itf_count = _runtime_dev_count_itfs(itf_desc); + uint16_t claim_len = _runtime_dev_claim_itfs(itf_desc, assoc_itf_count, max_len); + + // Call the Python callback to allow the driver to start working with these interface(s) + + if (mp_obj_is_callable(usbd->open_itf_cb)) { + // Repurpose the control_data memoryview to point into itf_desc for this one call + usbd->control_data->items = (void *)itf_desc; + usbd->control_data->len = claim_len; + mp_obj_t args[] = { MP_OBJ_FROM_PTR(usbd->control_data) }; + usbd_callback_function_n(usbd->open_itf_cb, 1, args); + usbd->control_data->len = 0; + usbd->control_data->items = NULL; + } + + return claim_len; +} + +static bool runtime_dev_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { + mp_obj_t cb_res = mp_const_false; + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + tusb_dir_t dir = request->bmRequestType_bit.direction; + mp_buffer_info_t buf_info; + + if (!usbd) { + return false; + } + + if (mp_obj_is_callable(usbd->control_xfer_cb)) { + usbd->control_data->items = (void *)request; + usbd->control_data->len = sizeof(tusb_control_request_t); + mp_obj_t args[] = { + mp_obj_new_int(stage), + MP_OBJ_FROM_PTR(usbd->control_data), + }; + cb_res = usbd_callback_function_n(usbd->control_xfer_cb, MP_ARRAY_SIZE(args), args); + usbd->control_data->items = NULL; + usbd->control_data->len = 0; + + if (cb_res == MP_OBJ_NULL) { + // Exception occurred in the callback handler, stall this transfer + cb_res = mp_const_false; + } + } + + // Check if callback returned any data to submit + if (mp_get_buffer(cb_res, &buf_info, dir == TUSB_DIR_IN ? MP_BUFFER_READ : MP_BUFFER_RW)) { + bool result = tud_control_xfer(USBD_RHPORT, + request, + buf_info.buf, + buf_info.len); + + if (result) { + // Keep buffer object alive until the transfer completes + usbd->xfer_data[0][dir] = cb_res; + } + + return result; + } else { + // Expect True or False to stall or continue + + if (stage == CONTROL_STAGE_ACK) { + // Allow data to be GCed once it's no longer in use + usbd->xfer_data[0][dir] = mp_const_none; + } + return mp_obj_is_true(cb_res); + } +} + +static bool runtime_dev_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + mp_obj_t ep = mp_obj_new_int(ep_addr); + mp_obj_t cb_res = mp_const_false; + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + if (!usbd) { + return false; + } + + if (mp_obj_is_callable(usbd->xfer_cb)) { + mp_obj_t args[] = { + ep, + MP_OBJ_NEW_SMALL_INT(result), + MP_OBJ_NEW_SMALL_INT(xferred_bytes), + }; + cb_res = usbd_callback_function_n(usbd->xfer_cb, MP_ARRAY_SIZE(args), args); + } + + // Clear any xfer_data for this endpoint + usbd->xfer_data[tu_edpt_number(ep_addr)][tu_edpt_dir(ep_addr)] = mp_const_none; + + return cb_res != MP_OBJ_NULL && mp_obj_is_true(cb_res); +} + +static usbd_class_driver_t const _runtime_dev_driver = +{ + #if CFG_TUSB_DEBUG >= 2 + .name = "runtime_dev", + #endif + .init = runtime_dev_init, + .reset = runtime_dev_reset, + .open = runtime_dev_open, + .control_xfer_cb = runtime_dev_control_xfer_cb, + .xfer_cb = runtime_dev_xfer_cb, + .sof = NULL +}; + +usbd_class_driver_t const *usbd_app_driver_get_cb(uint8_t *driver_count) { + *driver_count = 1; + return &_runtime_dev_driver; +} + + +// Functions below here (named mp_usbd_xyz) apply to the whole TinyUSB C-based subsystem +// and not necessarily the USBD singleton object (named usbd_xyz). + +// To support soft reset clearing USB runtime state, we manage three TinyUSB states: +// +// - "Not initialised" - tusb_inited() returns false, no USB at all). Only way +// back to this state is hard reset. +// +// - "Activated" - tusb_inited() returns true, USB device "connected" at device +// end and available to host. +// +// - "Deactivated" - tusb_inited() returns true, but USB device "disconnected" +// at device end and host can't see it. + + +// Top-level USB device subsystem init. +// +// Makes an on-demand call to mp_usbd_activate(), if USB is needed. +// +// This is called on any soft reset after boot.py runs, or on demand if the +// user activates USB and it hasn't activated yet. +void mp_usbd_init(void) { + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + bool need_usb; + + if (usbd == NULL) { + // No runtime USB device + #if CFG_TUD_CDC || CFG_TUD_MSC + // Builtin drivers are available, so initialise as defaults + need_usb = true; + #else + // No static drivers, nothing to initialise + need_usb = false; + #endif + } else { + // Otherwise, initialise based on whether runtime USB is active + need_usb = usbd->active; + } + + if (need_usb) { + tusb_init(); // Safe to call redundantly + tud_connect(); // Reconnect if mp_usbd_deinit() has disconnected + } +} + +// Top-level USB device deinit. +// +// This variant is called from soft reset, NULLs out the USB device +// singleton instance from MP_STATE_VM, and disconnects the port. +void mp_usbd_deinit(void) { + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + MP_STATE_VM(usbd) = MP_OBJ_NULL; + if (usbd) { + // Disconnect if a runtime USB device was active + mp_usbd_disconnect(usbd); + } +} + +// Thin wrapper around tud_disconnect() that tells TinyUSB all endpoints +// have stalled, to prevent it getting confused if a transfer is in progress. +static void mp_usbd_disconnect(mp_obj_usb_device_t *usbd) { + if (!tusb_inited()) { + return; // TinyUSB hasn't initialised + } + + if (usbd) { + // There might be USB transfers in progress right now, so need to stall any live + // endpoints + // + // TODO: figure out if we really need this + for (int epnum = 0; epnum < CFG_TUD_ENDPPOINT_MAX; epnum++) { + for (int dir = 0; dir < 2; dir++) { + if (usbd->xfer_data[epnum][dir] != mp_const_none) { + usbd_edpt_stall(USBD_RHPORT, tu_edpt_addr(epnum, dir)); + usbd->xfer_data[epnum][dir] = mp_const_none; + } + } + } + } + + #if MICROPY_HW_USB_CDC + // Ensure no pending static CDC writes, as these can cause TinyUSB to crash + tud_cdc_write_clear(); + #endif + + bool was_connected = tud_connected(); + tud_disconnect(); + if (was_connected) { + mp_hal_delay_ms(50); // TODO: Always??? + } +} + +// Thjs callback is queued by mp_usbd_schedule_task() to process USB later. +void mp_usbd_task_callback(mp_sched_node_t *node) { + if (tud_inited() && !in_usbd_task) { + mp_usbd_task_inner(); + } + // If in_usbd_task is set, it means something else has already manually called + // mp_usbd_task() (most likely: C-based USB-CDC serial port). Now the MP + // scheduler is running inside there and triggering this callback. It's OK + // to skip, the already-running outer TinyUSB task will process all pending + // events before it returns. +} + +// Task function can be called manually to force processing of USB events +// (mostly from USB-CDC serial port when blocking.) +void mp_usbd_task(void) { + if (in_usbd_task) { + // If this exception triggers, it means a USB callback tried to do + // something that itself became blocked on TinyUSB (most likely: read or + // write from a C-based USB-CDC serial port.) + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("TinyUSB callback can't recurse")); + } + + mp_usbd_task_inner(); +} + +static void mp_usbd_task_inner(void) { + in_usbd_task = true; + + tud_task_ext(0, false); + + mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd)); + + // Check for a triggered change to/from active state + if (usbd && usbd->trigger) { + if (usbd->active) { + if (tud_connected()) { + // If a SETUP packet has been received, first disconnect + // and wait for the host to recognise this and trigger a bus reset. + // + // Effectively this forces it to re-enumerate the device. + mp_usbd_disconnect(usbd); + } + tud_connect(); + } else { + mp_usbd_disconnect(usbd); + } + usbd->trigger = false; + } + + in_usbd_task = false; + + if (usbd) { + // Print any exceptions that were raised by Python callbacks + // inside tud_task_ext(). See usbd_callback_function_n. + + // As printing exceptions to USB-CDC may recursively call mp_usbd_task(), + // first copy out the pending data to the local stack + mp_uint_t num_pend_excs = usbd->num_pend_excs; + mp_obj_t pend_excs[MP_USBD_MAX_PEND_EXCS]; + for (mp_uint_t i = 0; i < MIN(MP_USBD_MAX_PEND_EXCS, num_pend_excs); i++) { + pend_excs[i] = usbd->pend_excs[i]; + usbd->pend_excs[i] = mp_const_none; + } + usbd->num_pend_excs = 0; + + // Now print the exceptions stored from this mp_usbd_task() call + for (mp_uint_t i = 0; i < MIN(MP_USBD_MAX_PEND_EXCS, num_pend_excs); i++) { + mp_obj_print_exception(&mp_plat_print, pend_excs[i]); + } + if (num_pend_excs > MP_USBD_MAX_PEND_EXCS) { + mp_printf(&mp_plat_print, "%u additional exceptions in USB callbacks\n", + num_pend_excs - MP_USBD_MAX_PEND_EXCS); + } + } +} + +#endif // MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE diff --git a/shared/tinyusb/tusb_config.h b/shared/tinyusb/tusb_config.h index 266cb88cc2..c5644e3d58 100644 --- a/shared/tinyusb/tusb_config.h +++ b/shared/tinyusb/tusb_config.h @@ -31,6 +31,10 @@ #if MICROPY_HW_ENABLE_USBDEV +#ifndef MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE +#define MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE 0 +#endif + #ifndef MICROPY_HW_USB_MANUFACTURER_STRING #define MICROPY_HW_USB_MANUFACTURER_STRING "MicroPython" #endif @@ -86,12 +90,9 @@ #define CFG_TUD_MSC_BUFSIZE (MICROPY_FATFS_MAX_SS) #endif -// Define static descriptor size and interface count based on the above config +#define USBD_RHPORT (0) // Currently only one port is supported -#define USBD_STATIC_DESC_LEN (TUD_CONFIG_DESC_LEN + \ - (CFG_TUD_CDC ? (TUD_CDC_DESC_LEN) : 0) + \ - (CFG_TUD_MSC ? (TUD_MSC_DESC_LEN) : 0) \ - ) +// Define built-in interface, string and endpoint numbering based on the above config #define USBD_STR_0 (0x00) #define USBD_STR_MANUF (0x01) @@ -126,19 +127,19 @@ #endif // CFG_TUD_CDC #endif // CFG_TUD_MSC -/* Limits of statically defined USB interfaces, endpoints, strings */ +/* Limits of builtin USB interfaces, endpoints, strings */ #if CFG_TUD_MSC -#define USBD_ITF_STATIC_MAX (USBD_ITF_MSC + 1) -#define USBD_STR_STATIC_MAX (USBD_STR_MSC + 1) -#define USBD_EP_STATIC_MAX (EPNUM_MSC_OUT + 1) +#define USBD_ITF_BUILTIN_MAX (USBD_ITF_MSC + 1) +#define USBD_STR_BUILTIN_MAX (USBD_STR_MSC + 1) +#define USBD_EP_BUILTIN_MAX (EPNUM_MSC_OUT + 1) #elif CFG_TUD_CDC -#define USBD_ITF_STATIC_MAX (USBD_ITF_CDC + 2) -#define USBD_STR_STATIC_MAX (USBD_STR_CDC + 1) -#define USBD_EP_STATIC_MAX (((EPNUM_CDC_EP_IN)&~TUSB_DIR_IN_MASK) + 1) +#define USBD_ITF_BUILTIN_MAX (USBD_ITF_CDC + 2) +#define USBD_STR_BUILTIN_MAX (USBD_STR_CDC + 1) +#define USBD_EP_BUILTIN_MAX (((USBD_CDC_EP_IN)&~TUSB_DIR_IN_MASK) + 1) #else // !CFG_TUD_MSC && !CFG_TUD_CDC -#define USBD_ITF_STATIC_MAX (0) -#define USBD_STR_STATIC_MAX (0) -#define USBD_EP_STATIC_MAX (0) +#define USBD_ITF_BUILTIN_MAX (0) +#define USBD_STR_BUILTIN_MAX (0) +#define USBD_EP_BUILTIN_MAX (0) #endif #endif // MICROPY_HW_ENABLE_USBDEV From 0baa3b5528257d2d8c3ada5842885d83b9acbb75 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 13 Mar 2024 10:38:05 +1100 Subject: [PATCH 12/49] rp2: Enable support for Python USB devices. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/rp2/CMakeLists.txt | 2 ++ ports/rp2/main.c | 4 ++++ ports/rp2/mpconfigport.h | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 392fed29b1..b605faaf21 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -108,6 +108,7 @@ set(MICROPY_SOURCE_LIB ${MICROPY_DIR}/shared/tinyusb/mp_cdc_common.c ${MICROPY_DIR}/shared/tinyusb/mp_usbd.c ${MICROPY_DIR}/shared/tinyusb/mp_usbd_descriptor.c + ${MICROPY_DIR}/shared/tinyusb/mp_usbd_runtime.c ) set(MICROPY_SOURCE_DRIVERS @@ -146,6 +147,7 @@ set(MICROPY_SOURCE_QSTR ${MICROPY_DIR}/shared/readline/readline.c ${MICROPY_DIR}/shared/runtime/mpirq.c ${MICROPY_DIR}/shared/runtime/sys_stdio_mphal.c + ${MICROPY_DIR}/shared/tinyusb/mp_usbd_runtime.c ${MICROPY_PORT_DIR}/machine_adc.c ${MICROPY_PORT_DIR}/machine_i2c.c ${MICROPY_PORT_DIR}/machine_pin.c diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 70a67066fc..40374faff9 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -221,6 +221,10 @@ int main(int argc, char **argv) { mp_thread_deinit(); #endif soft_timer_deinit(); + #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + mp_usbd_deinit(); + #endif + gc_sweep_all(); mp_deinit(); } diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index c11d2fa69d..a29692d0be 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -52,6 +52,10 @@ #ifndef MICROPY_HW_USB_MSC #define MICROPY_HW_USB_MSC (0) #endif + +#ifndef MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE +#define MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE (1) // Support machine.USBDevice +#endif #endif #ifndef MICROPY_CONFIG_ROM_LEVEL From 7f5d8c4605e9305b7d1dbb100d6704618f8f3b4b Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 23 Nov 2023 09:53:16 +1100 Subject: [PATCH 13/49] samd: Enable support for Python USB devices. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/samd/Makefile | 1 + ports/samd/main.c | 3 +++ ports/samd/mpconfigport.h | 5 +++++ 3 files changed, 9 insertions(+) diff --git a/ports/samd/Makefile b/ports/samd/Makefile index 5a7cb9916a..b678cd9828 100644 --- a/ports/samd/Makefile +++ b/ports/samd/Makefile @@ -136,6 +136,7 @@ SHARED_SRC_C += \ shared/tinyusb/mp_cdc_common.c \ shared/tinyusb/mp_usbd.c \ shared/tinyusb/mp_usbd_descriptor.c \ + shared/tinyusb/mp_usbd_runtime.c \ ASF4_SRC_C += $(addprefix lib/asf4/$(MCU_SERIES_LOWER)/,\ hal/src/hal_atomic.c \ diff --git a/ports/samd/main.c b/ports/samd/main.c index f051e961ff..9f213f27d4 100644 --- a/ports/samd/main.c +++ b/ports/samd/main.c @@ -93,6 +93,9 @@ void samd_main(void) { pwm_deinit_all(); #endif soft_timer_deinit(); + #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + mp_usbd_deinit(); + #endif gc_sweep_all(); #if MICROPY_PY_MACHINE_I2C || MICROPY_PY_MACHINE_SPI || MICROPY_PY_MACHINE_UART sercom_deinit_all(); diff --git a/ports/samd/mpconfigport.h b/ports/samd/mpconfigport.h index 28a99333d3..0b47500bf7 100644 --- a/ports/samd/mpconfigport.h +++ b/ports/samd/mpconfigport.h @@ -63,8 +63,13 @@ #ifndef MICROPY_HW_USB_DESC_STR_MAX #define MICROPY_HW_USB_DESC_STR_MAX (32) #endif +// Support machine.USBDevice +#ifndef MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE +#define MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE (1) #endif +#endif // MICROPY_HW_ENABLE_USBDEV + #define MICROPY_PY_SYS_PLATFORM "samd" // Extended modules From 5bed292c06ec712f501dda7e2cc202b7039c33b4 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 13 Nov 2023 17:17:57 +0100 Subject: [PATCH 14/49] lib/open-amp: Add OpenAMP submodule. OpenAMP framework provides a standard inter processor communications infrastructure for RTOS and bare metal environments. There are 3 major components in OpenAMP: libmetal, remoteproc and RPMsg. libmetal provides abstraction of the low-level underlying hardware, remoteproc is used for processor Life Cycle Management (LCM) like loading firmware, starting, stopping a core etc., and RPMsg is a bus infrastructure that enables Inter Processor Communications (IPC) between different cores. Signed-off-by: iabdalkader --- .gitmodules | 3 +++ lib/open-amp | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/open-amp diff --git a/.gitmodules b/.gitmodules index 75bffdaddd..39cf208412 100644 --- a/.gitmodules +++ b/.gitmodules @@ -59,3 +59,6 @@ [submodule "lib/protobuf-c"] path = lib/protobuf-c url = https://github.com/protobuf-c/protobuf-c.git +[submodule "lib/open-amp"] + path = lib/open-amp + url = https://github.com/OpenAMP/open-amp.git diff --git a/lib/open-amp b/lib/open-amp new file mode 160000 index 0000000000..1904dee18d --- /dev/null +++ b/lib/open-amp @@ -0,0 +1 @@ +Subproject commit 1904dee18da85400e72b8f55c5c99e872a486573 From f6213ffc5cb7ea1cb3c7ec10ccd9032035c12159 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Tue, 20 Feb 2024 12:28:36 +0100 Subject: [PATCH 15/49] lib/libmetal: Add libmetal submodule. libmetal provides an abstraction of the underlying hardware, to support other OpenAMP components. Signed-off-by: iabdalkader --- .gitmodules | 3 +++ lib/libmetal | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/libmetal diff --git a/.gitmodules b/.gitmodules index 39cf208412..aa9bb3d6d9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -62,3 +62,6 @@ [submodule "lib/open-amp"] path = lib/open-amp url = https://github.com/OpenAMP/open-amp.git +[submodule "lib/libmetal"] + path = lib/libmetal + url = https://github.com/OpenAMP/libmetal.git diff --git a/lib/libmetal b/lib/libmetal new file mode 160000 index 0000000000..0cb7d293a7 --- /dev/null +++ b/lib/libmetal @@ -0,0 +1 @@ +Subproject commit 0cb7d293a7f25394a06847a28d0f0ace9862936e From 61ee59ad8917d64f4f4a0e7a261fd838c5724014 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Tue, 20 Feb 2024 12:41:04 +0100 Subject: [PATCH 16/49] extmod/libmetal: Add MicroPython platform for libmetal. Add a MicroPython platform for libmetal, based on the generic platform. The MicroPython platform uses common mp_hal_xxx functions and allows ports to customize default configurations for libmetal. Signed-off-by: Jim Mussared Signed-off-by: iabdalkader --- extmod/libmetal/libmetal.mk | 51 ++++++++++++ extmod/libmetal/metal/config.h | 81 +++++++++++++++++++ .../libmetal/metal/system/micropython/alloc.h | 1 + .../libmetal/metal/system/micropython/log.h | 1 + .../libmetal/metal/system/micropython/sleep.h | 1 + .../libmetal/metal/system/micropython/sys.h | 5 ++ 6 files changed, 140 insertions(+) create mode 100644 extmod/libmetal/libmetal.mk create mode 100644 extmod/libmetal/metal/config.h create mode 100644 extmod/libmetal/metal/system/micropython/alloc.h create mode 100644 extmod/libmetal/metal/system/micropython/log.h create mode 100644 extmod/libmetal/metal/system/micropython/sleep.h create mode 100644 extmod/libmetal/metal/system/micropython/sys.h diff --git a/extmod/libmetal/libmetal.mk b/extmod/libmetal/libmetal.mk new file mode 100644 index 0000000000..852b042898 --- /dev/null +++ b/extmod/libmetal/libmetal.mk @@ -0,0 +1,51 @@ +# Makefile directives for libmetal + +# libmetal is intended to run through a pre-processor (as part of its CMake-based build system). +# This replicates the basic functionality of the pre-processor, including adding a "micropython" +# platform that is almost identical to the built-in "generic" platform with a few small changes +# provided by the files in extmod/libmetal. +$(BUILD)/openamp: $(BUILD) + $(MKDIR) -p $@ + +$(BUILD)/openamp/metal: $(BUILD)/openamp + $(MKDIR) -p $@ + +$(BUILD)/openamp/metal/config.h: $(BUILD)/openamp/metal $(TOP)/$(LIBMETAL_DIR)/lib/config.h + @$(ECHO) "GEN $@" + @for file in $(TOP)/$(LIBMETAL_DIR)/lib/*.c $(TOP)/$(LIBMETAL_DIR)/lib/*.h; do $(SED) -e "s/@PROJECT_SYSTEM@/micropython/g" -e "s/@PROJECT_PROCESSOR@/arm/g" $$file > $(BUILD)/openamp/metal/$$(basename $$file); done + $(MKDIR) -p $(BUILD)/openamp/metal/processor/arm + @$(CP) $(TOP)/$(LIBMETAL_DIR)/lib/processor/arm/*.h $(BUILD)/openamp/metal/processor/arm + $(MKDIR) -p $(BUILD)/openamp/metal/compiler/gcc + @$(CP) $(TOP)/$(LIBMETAL_DIR)/lib/compiler/gcc/*.h $(BUILD)/openamp/metal/compiler/gcc + $(MKDIR) -p $(BUILD)/openamp/metal/system/micropython + @$(CP) $(TOP)/$(LIBMETAL_DIR)/lib/system/generic/*.c $(TOP)/$(LIBMETAL_DIR)/lib/system/generic/*.h $(BUILD)/openamp/metal/system/micropython + @$(CP) $(TOP)/extmod/libmetal/metal/system/micropython/* $(BUILD)/openamp/metal/system/micropython + @$(CP) $(TOP)/extmod/libmetal/metal/config.h $(BUILD)/openamp/metal/config.h + +# libmetal's source files. +SRC_LIBMETAL_C := $(addprefix $(BUILD)/openamp/metal/,\ + device.c \ + dma.c \ + init.c \ + io.c \ + irq.c \ + log.c \ + shmem.c \ + softirq.c \ + version.c \ + device.c \ + system/micropython/condition.c \ + system/micropython/device.c \ + system/micropython/io.c \ + system/micropython/irq.c \ + system/micropython/shmem.c \ + system/micropython/time.c \ + ) + +# These files are generated by the rule above (along with config.h), so we need to make the .c +# files depend on that step -- can't use the .o files as the target because the .c files don't +# exist yet. +$(SRC_LIBMETAL_C): $(BUILD)/openamp/metal/config.h + +# Qstr processing will include the generated libmetal headers, so add them as a qstr requirement. +QSTR_GLOBAL_REQUIREMENTS += $(BUILD)/openamp/metal/config.h diff --git a/extmod/libmetal/metal/config.h b/extmod/libmetal/metal/config.h new file mode 100644 index 0000000000..459a2f4ca4 --- /dev/null +++ b/extmod/libmetal/metal/config.h @@ -0,0 +1,81 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 Arduino SA + * + * 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. + * + * MicroPython libmetal config. + */ +#ifndef MICROPY_INCLUDED_METAL_MICROPYTHON_H +#define MICROPY_INCLUDED_METAL_MICROPYTHON_H + +#include + +// Port-specific config. +#include "mpmetalport.h" + +// Library major version number. +#define METAL_VER_MAJOR 1 + +// Library minor version number. +#define METAL_VER_MINOR 5 + +// Library patch level. +#define METAL_VER_PATCH 0 + +// Library version string. +#define METAL_VER "1.5.0" + +#if METAL_HAVE_STDATOMIC_H +#define HAVE_STDATOMIC_H +#endif + +#if METAL_HAVE_FUTEX_H +#define HAVE_FUTEX_H +#endif + +#ifndef METAL_MAX_DEVICE_REGIONS +#define METAL_MAX_DEVICE_REGIONS 1 +#endif + +// generic/log.h +#if METAL_LOG_HANDLER_ENABLE +#include "py/mphal.h" +#undef metal_log +#define metal_log(level, ...) mp_printf(&mp_plat_print, __VA_ARGS__) +#endif + +static inline void *__metal_allocate_memory(unsigned int size) { + return m_tracked_calloc(1, size); +} + +static inline void __metal_free_memory(void *ptr) { + m_tracked_free(ptr); +} + +// The following functions must be provided by the port (in mpmetalport.h / mpmetalport.c). +int __metal_sleep_usec(unsigned int usec); +void sys_irq_enable(unsigned int vector); +void sys_irq_disable(unsigned int vector); +void sys_irq_restore_enable(unsigned int flags); +unsigned int sys_irq_save_disable(void); +#endif // MICROPY_INCLUDED_METAL_MICROPYTHON_H diff --git a/extmod/libmetal/metal/system/micropython/alloc.h b/extmod/libmetal/metal/system/micropython/alloc.h new file mode 100644 index 0000000000..dd0fdb13f9 --- /dev/null +++ b/extmod/libmetal/metal/system/micropython/alloc.h @@ -0,0 +1 @@ +#include diff --git a/extmod/libmetal/metal/system/micropython/log.h b/extmod/libmetal/metal/system/micropython/log.h new file mode 100644 index 0000000000..dd0fdb13f9 --- /dev/null +++ b/extmod/libmetal/metal/system/micropython/log.h @@ -0,0 +1 @@ +#include diff --git a/extmod/libmetal/metal/system/micropython/sleep.h b/extmod/libmetal/metal/system/micropython/sleep.h new file mode 100644 index 0000000000..dd0fdb13f9 --- /dev/null +++ b/extmod/libmetal/metal/system/micropython/sleep.h @@ -0,0 +1 @@ +#include diff --git a/extmod/libmetal/metal/system/micropython/sys.h b/extmod/libmetal/metal/system/micropython/sys.h new file mode 100644 index 0000000000..0f404dcd61 --- /dev/null +++ b/extmod/libmetal/metal/system/micropython/sys.h @@ -0,0 +1,5 @@ +#include + +struct metal_state { + struct metal_common_state common; +}; From 85028aadab6763a0d899e71f54643840d9f8546d Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 20 Nov 2023 14:24:04 +0100 Subject: [PATCH 17/49] py/stream: Add mp_stream_seek() helper function. Signed-off-by: iabdalkader --- py/stream.c | 12 ++++++++++++ py/stream.h | 1 + 2 files changed, 13 insertions(+) diff --git a/py/stream.c b/py/stream.c index aa905ca8cc..e008fb999e 100644 --- a/py/stream.c +++ b/py/stream.c @@ -82,6 +82,18 @@ mp_uint_t mp_stream_rw(mp_obj_t stream, void *buf_, mp_uint_t size, int *errcode return done; } +mp_uint_t mp_stream_seek(mp_obj_t stream, mp_off_t offset, int whence, int *errcode) { + struct mp_stream_seek_t seek_s; + seek_s.offset = offset; + seek_s.whence = whence; + const mp_stream_p_t *stream_p = mp_get_stream(stream); + mp_uint_t res = stream_p->ioctl(MP_OBJ_FROM_PTR(stream), MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, errcode); + if (res == MP_STREAM_ERROR) { + return -1; + } + return seek_s.offset; +} + const mp_stream_p_t *mp_get_stream_raise(mp_obj_t self_in, int flags) { const mp_obj_type_t *type = mp_obj_get_type(self_in); if (MP_OBJ_TYPE_HAS_SLOT(type, protocol)) { diff --git a/py/stream.h b/py/stream.h index e6e6f283df..7c2e87a87c 100644 --- a/py/stream.h +++ b/py/stream.h @@ -115,6 +115,7 @@ mp_obj_t mp_stream_write(mp_obj_t self_in, const void *buf, size_t len, byte fla mp_uint_t mp_stream_rw(mp_obj_t stream, void *buf, mp_uint_t size, int *errcode, byte flags); #define mp_stream_write_exactly(stream, buf, size, err) mp_stream_rw(stream, (byte *)buf, size, err, MP_STREAM_RW_WRITE) #define mp_stream_read_exactly(stream, buf, size, err) mp_stream_rw(stream, buf, size, err, MP_STREAM_RW_READ) +mp_uint_t mp_stream_seek(mp_obj_t stream, mp_off_t offset, int whence, int *errcode); void mp_stream_write_adaptor(void *self, const char *buf, size_t len); From b72602250929234dbdbba084ca0dc4fc94b056ae Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 29 Feb 2024 15:21:56 +1100 Subject: [PATCH 18/49] py/stream: Factor stream implementations. So there's only one location that does the ioctl(MP_STREAM_SEEK) call. Signed-off-by: Damien George --- py/stream.c | 31 ++++++++++++------------------- py/stream.h | 2 +- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/py/stream.c b/py/stream.c index e008fb999e..d7a8881e1a 100644 --- a/py/stream.c +++ b/py/stream.c @@ -82,14 +82,14 @@ mp_uint_t mp_stream_rw(mp_obj_t stream, void *buf_, mp_uint_t size, int *errcode return done; } -mp_uint_t mp_stream_seek(mp_obj_t stream, mp_off_t offset, int whence, int *errcode) { +mp_off_t mp_stream_seek(mp_obj_t stream, mp_off_t offset, int whence, int *errcode) { struct mp_stream_seek_t seek_s; seek_s.offset = offset; seek_s.whence = whence; const mp_stream_p_t *stream_p = mp_get_stream(stream); mp_uint_t res = stream_p->ioctl(MP_OBJ_FROM_PTR(stream), MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, errcode); if (res == MP_STREAM_ERROR) { - return -1; + return (mp_off_t)-1; } return seek_s.offset; } @@ -457,28 +457,26 @@ static mp_obj_t mp_stream___exit__(size_t n_args, const mp_obj_t *args) { MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream___exit___obj, 4, 4, mp_stream___exit__); static mp_obj_t stream_seek(size_t n_args, const mp_obj_t *args) { - struct mp_stream_seek_t seek_s; // TODO: Could be uint64 - seek_s.offset = mp_obj_get_int(args[1]); - seek_s.whence = SEEK_SET; + mp_off_t offset = mp_obj_get_int(args[1]); + int whence = SEEK_SET; if (n_args == 3) { - seek_s.whence = mp_obj_get_int(args[2]); + whence = mp_obj_get_int(args[2]); } // In POSIX, it's error to seek before end of stream, we enforce it here. - if (seek_s.whence == SEEK_SET && seek_s.offset < 0) { + if (whence == SEEK_SET && offset < 0) { mp_raise_OSError(MP_EINVAL); } - const mp_stream_p_t *stream_p = mp_get_stream(args[0]); int error; - mp_uint_t res = stream_p->ioctl(args[0], MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &error); - if (res == MP_STREAM_ERROR) { + mp_off_t res = mp_stream_seek(args[0], offset, whence, &error); + if (res == (mp_off_t)-1) { mp_raise_OSError(error); } // TODO: Could be uint64 - return mp_obj_new_int_from_uint(seek_s.offset); + return mp_obj_new_int_from_uint(res); } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_seek_obj, 2, 3, stream_seek); @@ -557,16 +555,11 @@ ssize_t mp_stream_posix_read(void *stream, void *buf, size_t len) { } off_t mp_stream_posix_lseek(void *stream, off_t offset, int whence) { - const mp_obj_base_t *o = stream; - const mp_stream_p_t *stream_p = MP_OBJ_TYPE_GET_SLOT(o->type, protocol); - struct mp_stream_seek_t seek_s; - seek_s.offset = offset; - seek_s.whence = whence; - mp_uint_t res = stream_p->ioctl(MP_OBJ_FROM_PTR(stream), MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &errno); - if (res == MP_STREAM_ERROR) { + mp_off_t res = mp_stream_seek(MP_OBJ_FROM_PTR(stream), offset, whence, &errno); + if (res == (mp_off_t)-1) { return -1; } - return seek_s.offset; + return res; } int mp_stream_posix_fsync(void *stream) { diff --git a/py/stream.h b/py/stream.h index 7c2e87a87c..7c4d38afa9 100644 --- a/py/stream.h +++ b/py/stream.h @@ -115,7 +115,7 @@ mp_obj_t mp_stream_write(mp_obj_t self_in, const void *buf, size_t len, byte fla mp_uint_t mp_stream_rw(mp_obj_t stream, void *buf, mp_uint_t size, int *errcode, byte flags); #define mp_stream_write_exactly(stream, buf, size, err) mp_stream_rw(stream, (byte *)buf, size, err, MP_STREAM_RW_WRITE) #define mp_stream_read_exactly(stream, buf, size, err) mp_stream_rw(stream, buf, size, err, MP_STREAM_RW_READ) -mp_uint_t mp_stream_seek(mp_obj_t stream, mp_off_t offset, int whence, int *errcode); +mp_off_t mp_stream_seek(mp_obj_t stream, mp_off_t offset, int whence, int *errcode); void mp_stream_write_adaptor(void *self, const char *buf, size_t len); From 162054be85a58466edea2297abba9aadc6c3d597 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 11 Dec 2023 13:19:07 +0100 Subject: [PATCH 19/49] stm32/mpu: Add MPU config for shared, uncached memory region. Signed-off-by: iabdalkader --- ports/stm32/mpu.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ports/stm32/mpu.h b/ports/stm32/mpu.h index 64880a85db..5ef1466184 100644 --- a/ports/stm32/mpu.h +++ b/ports/stm32/mpu.h @@ -36,6 +36,7 @@ #define MPU_REGION_QSPI3 (MPU_REGION_NUMBER3) #define MPU_REGION_SDRAM1 (MPU_REGION_NUMBER4) #define MPU_REGION_SDRAM2 (MPU_REGION_NUMBER5) +#define MPU_REGION_OPENAMP (MPU_REGION_NUMBER15) // Only relevant on CPUs with D-Cache, must be higher priority than SDRAM #define MPU_REGION_DMA_UNCACHED_1 (MPU_REGION_NUMBER6) @@ -94,6 +95,18 @@ | MPU_REGION_ENABLE << MPU_RASR_ENABLE_Pos \ ) +#define MPU_CONFIG_SHARED_UNCACHED(size) ( \ + MPU_INSTRUCTION_ACCESS_DISABLE << MPU_RASR_XN_Pos \ + | MPU_REGION_FULL_ACCESS << MPU_RASR_AP_Pos \ + | MPU_TEX_LEVEL1 << MPU_RASR_TEX_Pos \ + | MPU_ACCESS_SHAREABLE << MPU_RASR_S_Pos \ + | MPU_ACCESS_NOT_CACHEABLE << MPU_RASR_C_Pos \ + | MPU_ACCESS_NOT_BUFFERABLE << MPU_RASR_B_Pos \ + | 0x00 << MPU_RASR_SRD_Pos \ + | (size) << MPU_RASR_SIZE_Pos \ + | MPU_REGION_ENABLE << MPU_RASR_ENABLE_Pos \ + ) + static inline void mpu_init(void) { MPU->CTRL = MPU_PRIVILEGED_DEFAULT | MPU_CTRL_ENABLE_Msk; SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk; From 14dae42fe7a4c8c147d528e998e10a6a80d28ae5 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Tue, 20 Feb 2024 14:07:13 +0100 Subject: [PATCH 20/49] extmod/modopenamp: Add new OpenAMP module. This module implements OpenAMP's basic initialization and shared resources support, and provides support for OpenAMP's RPMsg component, by providing an `endpoint` type (a logical connection on top of RPMsg channel) which can be used to communicate with the remote core. Signed-off-by: iabdalkader --- extmod/extmod.mk | 42 +++++ extmod/modopenamp.c | 395 ++++++++++++++++++++++++++++++++++++++++++++ extmod/modopenamp.h | 81 +++++++++ 3 files changed, 518 insertions(+) create mode 100644 extmod/modopenamp.c create mode 100644 extmod/modopenamp.h diff --git a/extmod/extmod.mk b/extmod/extmod.mk index 4f5f7d53ac..54689ef65e 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -31,6 +31,7 @@ SRC_EXTMOD_C += \ extmod/modmachine.c \ extmod/modnetwork.c \ extmod/modonewire.c \ + extmod/modopenamp.c \ extmod/modos.c \ extmod/modplatform.c\ extmod/modrandom.c \ @@ -515,3 +516,44 @@ include $(TOP)/extmod/btstack/btstack.mk endif endif + +################################################################################ +# openamp + +ifeq ($(MICROPY_PY_OPENAMP),1) +OPENAMP_DIR = lib/open-amp +LIBMETAL_DIR = lib/libmetal +GIT_SUBMODULES += $(LIBMETAL_DIR) $(OPENAMP_DIR) +include $(TOP)/extmod/libmetal/libmetal.mk + +INC += -I$(TOP)/$(OPENAMP_DIR) +CFLAGS += -DMICROPY_PY_OPENAMP=1 + +CFLAGS_THIRDPARTY += \ + -I$(BUILD)/openamp \ + -I$(TOP)/$(OPENAMP_DIR) \ + -I$(TOP)/$(OPENAMP_DIR)/lib/include/ \ + -DMETAL_INTERNAL \ + -DVIRTIO_DRIVER_ONLY \ + -DNO_ATOMIC_64_SUPPORT \ + -DRPMSG_BUFFER_SIZE=512 \ + +# OpenAMP's source files. +SRC_OPENAMP_C += $(addprefix $(OPENAMP_DIR)/lib/,\ + rpmsg/rpmsg.c \ + rpmsg/rpmsg_virtio.c \ + virtio/virtio.c \ + virtio/virtqueue.c \ + virtio_mmio/virtio_mmio_drv.c \ + ) + +# Disable compiler warnings in OpenAMP (variables used only for assert). +$(BUILD)/$(OPENAMP_DIR)/lib/rpmsg/rpmsg_virtio.o: CFLAGS += -Wno-unused-but-set-variable +$(BUILD)/$(OPENAMP_DIR)/lib/virtio_mmio/virtio_mmio_drv.o: CFLAGS += -Wno-unused-but-set-variable + +# We need to have generated libmetal before compiling OpenAMP. +$(addprefix $(BUILD)/, $(SRC_OPENAMP_C:.c=.o)): $(BUILD)/openamp/metal/config.h + +SRC_THIRDPARTY_C += $(SRC_LIBMETAL_C) $(SRC_OPENAMP_C) + +endif # MICROPY_PY_OPENAMP diff --git a/extmod/modopenamp.c b/extmod/modopenamp.c new file mode 100644 index 0000000000..b0c834c281 --- /dev/null +++ b/extmod/modopenamp.c @@ -0,0 +1,395 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023-2024 Arduino SA + * + * 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. + * + * OpenAMP's Python module. + */ + +#if MICROPY_PY_OPENAMP + +#include "py/obj.h" +#include "py/nlr.h" +#include "py/runtime.h" + +#include "metal/sys.h" +#include "metal/alloc.h" +#include "metal/errno.h" +#include "metal/io.h" +#include "metal/device.h" +#include "metal/utilities.h" + +#include "openamp/open_amp.h" +#include "openamp/remoteproc.h" +#include "openamp/remoteproc_loader.h" +#include "modopenamp.h" + +#if !MICROPY_ENABLE_FINALISER +#error "MICROPY_PY_OPENAMP requires MICROPY_ENABLE_FINALISER" +#endif + +#if MICROPY_PY_OPENAMP_RSC_TABLE_ENABLE +#define VIRTIO_DEV_ID 0xFF +#define VIRTIO_DEV_FEATURES (1 << VIRTIO_RPMSG_F_NS) + +#define VRING0_ID 0 // VRING0 ID (host to remote) fixed to 0 for linux compatibility +#define VRING1_ID 1 // VRING1 ID (remote to host) fixed to 1 for linux compatibility +#define VRING_NOTIFY_ID VRING0_ID + +#define VRING_COUNT 2 +#define VRING_ALIGNMENT 32 +// Note the number of buffers must be a power of 2 +#define VRING_NUM_BUFFS 64 + +// The following config should be enough for about 128 descriptors. +// See lib/include/openamp/virtio_ring.h for the layout of vrings +// and vring_size() to calculate the vring size. +#define VRING_RX_ADDR (METAL_SHM_ADDR) +#define VRING_TX_ADDR (METAL_SHM_ADDR + 0x1000) +#define VRING_BUFF_ADDR (METAL_SHM_ADDR + 0x2000) +#define VRING_BUFF_SIZE (METAL_SHM_SIZE - 0x2000) + +static const char openamp_trace_buf[128]; +#define MICROPY_PY_OPENAMP_TRACE_BUF ((uint32_t)openamp_trace_buf) +#define MICROPY_PY_OPENAMP_TRACE_BUF_LEN sizeof(MICROPY_PY_OPENAMP_TRACE_BUF) + +#endif // MICROPY_PY_OPENAMP_RSC_TABLE_ENABLE + +#define debug_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__) + +static struct metal_device shm_device = { + .name = METAL_SHM_NAME, + // The number of IO regions is fixed and must match the number and + // layout of the remote processor's IO regions. The first region is + // used for the vring shared memory, and the second one is used for + // the shared resource table. + .num_regions = METAL_MAX_DEVICE_REGIONS, + .regions = { { 0 } }, + .node = { NULL }, + .irq_num = 0, + .irq_info = NULL +}; +static metal_phys_addr_t shm_physmap[] = { 0 }; + +// ###################### Virtio device class ###################### +typedef struct _virtio_dev_obj_t { + mp_obj_base_t base; + struct rpmsg_virtio_device rvdev; + struct rpmsg_virtio_shm_pool shm_pool; + mp_obj_t ns_callback; +} virtio_dev_obj_t; + +static mp_obj_t virtio_dev_deinit(mp_obj_t self_in) { + virtio_dev_obj_t *virtio_device = MP_OBJ_TO_PTR(self_in); + rpmsg_deinit_vdev(&virtio_device->rvdev); + metal_finish(); + MP_STATE_PORT(virtio_device) = NULL; + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(virtio_dev_deinit_obj, virtio_dev_deinit); + +static const mp_rom_map_elem_t virtio_dev_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_VirtIODev) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&virtio_dev_deinit_obj) }, +}; +static MP_DEFINE_CONST_DICT(virtio_dev_locals_dict, virtio_dev_locals_dict_table); + +static MP_DEFINE_CONST_OBJ_TYPE( + virtio_dev_type, + MP_QSTR_VirtIODev, + MP_TYPE_FLAG_NONE, + locals_dict, &virtio_dev_locals_dict + ); + +// ###################### RPMsg Endpoint class ###################### +typedef struct _endpoint_obj_t { + mp_obj_base_t base; + mp_obj_t name; + mp_obj_t callback; + struct rpmsg_endpoint ep; +} endpoint_obj_t; + +static const mp_obj_type_t endpoint_type; + +static int endpoint_recv_callback(struct rpmsg_endpoint *ept, void *data, size_t len, uint32_t src, void *priv) { + debug_printf("endpoint_recv_callback() message received src: %lu msg len: %d\n", src, len); + endpoint_obj_t *self = metal_container_of(ept, endpoint_obj_t, ep); + if (self->callback != mp_const_none) { + mp_call_function_2(self->callback, mp_obj_new_int(src), mp_obj_new_bytearray_by_ref(len, data)); + } + return 0; +} + +static mp_obj_t endpoint_send(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_src, ARG_dest, ARG_timeout }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_src, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + { MP_QSTR_dest, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + { MP_QSTR_timeout, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 2, pos_args + 2, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + endpoint_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + + if (is_rpmsg_ept_ready(&self->ep) == false) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Endpoint not ready")); + } + + uint32_t src = self->ep.addr; + if (args[ARG_src].u_int != -1) { + src = args[ARG_src].u_int; + } + + uint32_t dest = self->ep.dest_addr; + if (args[ARG_dest].u_int != -1) { + dest = args[ARG_dest].u_int; + } + + mp_buffer_info_t rbuf; + mp_get_buffer_raise(pos_args[1], &rbuf, MP_BUFFER_READ); + debug_printf("endpoint_send() msg len: %d\n", rbuf.len); + + int bytes = 0; + mp_int_t timeout = args[ARG_timeout].u_int; + for (mp_uint_t start = mp_hal_ticks_ms(); ;) { + bytes = rpmsg_send_offchannel_raw(&self->ep, src, dest, rbuf.buf, rbuf.len, false); + if (bytes > 0 || timeout == 0) { + MICROPY_EVENT_POLL_HOOK + break; + } + if (timeout > 0 && (mp_hal_ticks_ms() - start > timeout)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("timeout waiting for a free buffer")); + } + MICROPY_EVENT_POLL_HOOK + } + return mp_obj_new_int(bytes); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(endpoint_send_obj, 2, endpoint_send); + +static mp_obj_t endpoint_is_ready(mp_obj_t self_in) { + endpoint_obj_t *self = MP_OBJ_TO_PTR(self_in); + return is_rpmsg_ept_ready(&self->ep) ? mp_const_true : mp_const_false; +} +static MP_DEFINE_CONST_FUN_OBJ_1(endpoint_is_ready_obj, endpoint_is_ready); + +static mp_obj_t endpoint_deinit(mp_obj_t self_in) { + endpoint_obj_t *self = MP_OBJ_TO_PTR(self_in); + rpmsg_destroy_ept(&self->ep); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(endpoint_deinit_obj, endpoint_deinit); + +static mp_obj_t endpoint_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_name, ARG_callback, ARG_src, ARG_dest }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_name, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_callback, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_src, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = RPMSG_ADDR_ANY } }, + { MP_QSTR_dest, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = RPMSG_ADDR_ANY } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + endpoint_obj_t *self = mp_obj_malloc_with_finaliser(endpoint_obj_t, &endpoint_type); + self->name = args[ARG_name].u_obj; + self->callback = args[ARG_callback].u_obj; + + if (MP_STATE_PORT(virtio_device) == NULL) { + openamp_init(); + } + + if (rpmsg_create_ept(&self->ep, &MP_STATE_PORT(virtio_device)->rvdev.rdev, mp_obj_str_get_str(self->name), + args[ARG_src].u_int, args[ARG_dest].u_int, endpoint_recv_callback, NULL) != 0) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to create RPMsg endpoint")); + } + + return MP_OBJ_FROM_PTR(self); +} + +static const mp_rom_map_elem_t endpoint_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_Endpoint) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&endpoint_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&endpoint_send_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_ready), MP_ROM_PTR(&endpoint_is_ready_obj) }, +}; +static MP_DEFINE_CONST_DICT(endpoint_locals_dict, endpoint_locals_dict_table); + +static MP_DEFINE_CONST_OBJ_TYPE( + endpoint_type, + MP_QSTR_Endpoint, + MP_TYPE_FLAG_NONE, + make_new, endpoint_make_new, + locals_dict, &endpoint_locals_dict + ); + +// ###################### openamp module ###################### +void openamp_remoteproc_notified(mp_sched_node_t *node) { + (void)node; + rproc_virtio_notified(MP_STATE_PORT(virtio_device)->rvdev.vdev, VRING_NOTIFY_ID); +} + +static void openamp_ns_callback(struct rpmsg_device *rdev, const char *name, uint32_t dest) { + debug_printf("rpmsg_new_service_callback() new service request name: %s dest %lu\n", name, dest); + // The remote processor advertises its presence to the host by sending + // the Name Service (NS) announcement containing the name of the channel. + virtio_dev_obj_t *virtio_device = metal_container_of(rdev, virtio_dev_obj_t, rvdev); + if (virtio_device->ns_callback != mp_const_none) { + mp_call_function_2(virtio_device->ns_callback, mp_obj_new_int(dest), mp_obj_new_str(name, strlen(name))); + } +} + +#if MICROPY_PY_OPENAMP_RSC_TABLE_ENABLE +// The shared resource table must be initialized manually by the host here, +// because it's not located in the data region, so the startup code doesn't +// know about it. +static void openamp_rsc_table_init(openamp_rsc_table_t **rsc_table_out) { + openamp_rsc_table_t *rsc_table = METAL_RSC_ADDR; + memset(rsc_table, 0, METAL_RSC_SIZE); + + rsc_table->version = 1; + rsc_table->num = MP_ARRAY_SIZE(rsc_table->offset); + rsc_table->offset[0] = offsetof(openamp_rsc_table_t, vdev); + #if MICROPY_PY_OPENAMP_TRACE_BUF_ENABLE + rsc_table->offset[1] = offsetof(openamp_rsc_table_t, trace); + #endif + rsc_table->vdev = (struct fw_rsc_vdev) { + RSC_VDEV, VIRTIO_ID_RPMSG, 0, VIRTIO_DEV_FEATURES, 0, 0, 0, VRING_COUNT, {0, 0} + }; + rsc_table->vring0 = (struct fw_rsc_vdev_vring) { + VRING_TX_ADDR, VRING_ALIGNMENT, VRING_NUM_BUFFS, VRING0_ID, 0 + }; + rsc_table->vring1 = (struct fw_rsc_vdev_vring) { + VRING_RX_ADDR, VRING_ALIGNMENT, VRING_NUM_BUFFS, VRING1_ID, 0 + }; + #if MICROPY_PY_OPENAMP_TRACE_BUF_ENABLE + rsc_table->trace = (struct fw_rsc_trace) { + RSC_TRACE, MICROPY_PY_OPENAMP_TRACE_BUF, MICROPY_PY_OPENAMP_TRACE_BUF_LEN, 0, "trace_buf" + }; + #endif + #ifdef VIRTIO_USE_DCACHE + // Flush resource table. + metal_cache_flush((uint32_t *)rsc_table, sizeof(openamp_rsc_table_t)); + #endif + *rsc_table_out = rsc_table; +} +#endif // MICROPY_PY_OPENAMP_RSC_TABLE_ENABLE + +static mp_obj_t openamp_new_service_callback(mp_obj_t ns_callback) { + if (MP_STATE_PORT(virtio_device) == NULL) { + openamp_init(); + } + if (ns_callback != mp_const_none && !mp_obj_is_callable(ns_callback)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid callback")); + } + MP_STATE_PORT(virtio_device)->ns_callback = ns_callback; + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(openamp_new_service_callback_obj, openamp_new_service_callback); + +void openamp_init(void) { + if (MP_STATE_PORT(virtio_device) != NULL) { + // Already initialized. + return; + } + + struct metal_device *device; + struct metal_init_params metal_params = METAL_INIT_DEFAULTS; + + // Initialize libmetal. + metal_init(&metal_params); + + // Initialize the shared resource table. + openamp_rsc_table_t *rsc_table; + openamp_rsc_table_init(&rsc_table); + + if (metal_register_generic_device(&shm_device) != 0) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to register metal device")); + } + + if (metal_device_open("generic", METAL_SHM_NAME, &device) != 0) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to open metal device")); + } + + // Initialize shared memory IO region. + metal_io_init(&device->regions[0], (void *)METAL_SHM_ADDR, (void *)shm_physmap, METAL_SHM_SIZE, -1U, 0, NULL); + struct metal_io_region *shm_io = metal_device_io_region(device, 0); + if (shm_io == NULL) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to initialize device io region")); + } + + // Initialize resource table IO region. + metal_io_init(&device->regions[1], (void *)rsc_table, (void *)rsc_table, sizeof(*rsc_table), -1U, 0, NULL); + struct metal_io_region *rsc_io = metal_device_io_region(device, 1); + if (rsc_io == NULL) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to initialize device io region")); + } + + // Create virtio device. + struct virtio_device *vdev = rproc_virtio_create_vdev(RPMSG_HOST, VIRTIO_DEV_ID, + &rsc_table->vdev, rsc_io, NULL, metal_rproc_notify, NULL); + if (vdev == NULL) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to create virtio device")); + } + + // Initialize vrings. + struct fw_rsc_vdev_vring *vring_rsc = &rsc_table->vring0; + for (int i = 0; i < VRING_COUNT; i++, vring_rsc++) { + if (rproc_virtio_init_vring(vdev, vring_rsc->notifyid, vring_rsc->notifyid, + (void *)vring_rsc->da, shm_io, vring_rsc->num, vring_rsc->align) != 0) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to initialize vrings")); + } + } + + virtio_dev_obj_t *virtio_device = mp_obj_malloc_with_finaliser(virtio_dev_obj_t, &virtio_dev_type); + virtio_device->ns_callback = mp_const_none; + + // The remote processor detects that the virtio device is ready by polling + // the status field in the resource table. + rpmsg_virtio_init_shm_pool(&virtio_device->shm_pool, (void *)VRING_BUFF_ADDR, (size_t)VRING_BUFF_SIZE); + rpmsg_init_vdev(&virtio_device->rvdev, vdev, openamp_ns_callback, shm_io, &virtio_device->shm_pool); + + MP_STATE_PORT(virtio_device) = virtio_device; +} + +static const mp_rom_map_elem_t globals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_openamp) }, + { MP_ROM_QSTR(MP_QSTR_ENDPOINT_ADDR_ANY), MP_ROM_INT(RPMSG_ADDR_ANY) }, + { MP_ROM_QSTR(MP_QSTR_new_service_callback), MP_ROM_PTR(&openamp_new_service_callback_obj) }, + { MP_ROM_QSTR(MP_QSTR_Endpoint), MP_ROM_PTR(&endpoint_type) }, +}; +static MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); + +const mp_obj_module_t openamp_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_t)&globals_dict, +}; + +MP_REGISTER_ROOT_POINTER(struct _virtio_dev_obj_t *virtio_device); +MP_REGISTER_MODULE(MP_QSTR_openamp, openamp_module); + +#endif // MICROPY_PY_OPENAMP diff --git a/extmod/modopenamp.h b/extmod/modopenamp.h new file mode 100644 index 0000000000..750f689297 --- /dev/null +++ b/extmod/modopenamp.h @@ -0,0 +1,81 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Arduino SA + * + * 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. + * + * OpenAMP's Python module. + */ +#ifndef MICROPY_INCLUDED_MODOPENAMP_H +#define MICROPY_INCLUDED_MODOPENAMP_H + +// Include a port config file if one is defined. +#ifdef MICROPY_PY_OPENAMP_CONFIG_FILE +#include MICROPY_PY_OPENAMP_CONFIG_FILE +#endif + +// Use the default resource table layout and initialization code. +// Note ports and boards can override the default table and provide +// a custom one in openamp_rsc_table_t and openamp_rsc_table_init() +#ifndef MICROPY_PY_OPENAMP_RSC_TABLE_ENABLE +#define MICROPY_PY_OPENAMP_RSC_TABLE_ENABLE (1) +#endif + +// This enables a trace buffer that can be used by remote processor for +// writing trace logs. This is enabled by default to increase the default +// resource table's compatibility with common OpenAMP examples. +#ifndef MICROPY_PY_OPENAMP_TRACE_BUF_ENABLE +#define MICROPY_PY_OPENAMP_TRACE_BUF_ENABLE (1) +#endif + +// The resource table is used for sharing the configuration of the virtio +// device, vrings and other resources, between the host and remote cores. +// The layout and address the table structure must match the one used in +// the remote processor's firmware. +#if MICROPY_PY_OPENAMP_RSC_TABLE_ENABLE +typedef struct openamp_rsc_table { + unsigned int version; + unsigned int num; + unsigned int reserved[2]; + #if MICROPY_PY_OPENAMP_TRACE_BUF_ENABLE + unsigned int offset[2]; + #else + unsigned int offset[1]; + #endif + struct fw_rsc_vdev vdev; + struct fw_rsc_vdev_vring vring0; + struct fw_rsc_vdev_vring vring1; + #if MICROPY_PY_OPENAMP_TRACE_BUF_ENABLE + struct fw_rsc_trace trace; + #endif +} openamp_rsc_table_t; +#endif // MICROPY_PY_OPENAMP_RSC_TABLE_ENABLE + +// Performs low-level initialization of OpenAMP, such as initializing libmetal, +// the shared resource table, shared memory regions, vrings and virtio device. +void openamp_init(void); + +// Ports should run this callback in scheduler context, when +// the remote processor notifies the host of pending messages. +void openamp_remoteproc_notified(mp_sched_node_t *node); + +#endif // MICROPY_INCLUDED_MODOPENAMP_H From 81aba8253ae3be7090cc0fcb4702f38cdd0c50c8 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Tue, 20 Feb 2024 14:09:42 +0100 Subject: [PATCH 21/49] extmod/modopenamp_remoteproc: Add new OpenAMP RemoteProc class. RemoteProc provides an API to load firmware and control remote processors. Note: port-specific operations must be implemented to support this class. Signed-off-by: iabdalkader --- extmod/extmod.mk | 16 +++ extmod/modopenamp.c | 7 ++ extmod/modopenamp.h | 12 ++ extmod/modopenamp_remoteproc.c | 175 +++++++++++++++++++++++++++ extmod/modopenamp_remoteproc.h | 46 +++++++ extmod/modopenamp_remoteproc_store.c | 146 ++++++++++++++++++++++ 6 files changed, 402 insertions(+) create mode 100644 extmod/modopenamp_remoteproc.c create mode 100644 extmod/modopenamp_remoteproc.h create mode 100644 extmod/modopenamp_remoteproc_store.c diff --git a/extmod/extmod.mk b/extmod/extmod.mk index 54689ef65e..bf686fbbb4 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -32,6 +32,8 @@ SRC_EXTMOD_C += \ extmod/modnetwork.c \ extmod/modonewire.c \ extmod/modopenamp.c \ + extmod/modopenamp_remoteproc.c \ + extmod/modopenamp_remoteproc_store.c \ extmod/modos.c \ extmod/modplatform.c\ extmod/modrandom.c \ @@ -529,6 +531,10 @@ include $(TOP)/extmod/libmetal/libmetal.mk INC += -I$(TOP)/$(OPENAMP_DIR) CFLAGS += -DMICROPY_PY_OPENAMP=1 +ifeq ($(MICROPY_PY_OPENAMP_REMOTEPROC),1) +CFLAGS += -DMICROPY_PY_OPENAMP_REMOTEPROC=1 +endif + CFLAGS_THIRDPARTY += \ -I$(BUILD)/openamp \ -I$(TOP)/$(OPENAMP_DIR) \ @@ -547,6 +553,16 @@ SRC_OPENAMP_C += $(addprefix $(OPENAMP_DIR)/lib/,\ virtio_mmio/virtio_mmio_drv.c \ ) +# OpenAMP's remoteproc source files. +ifeq ($(MICROPY_PY_OPENAMP_REMOTEPROC),1) +SRC_OPENAMP_C += $(addprefix $(OPENAMP_DIR)/lib/remoteproc/,\ + elf_loader.c \ + remoteproc.c \ + remoteproc_virtio.c \ + rsc_table_parser.c \ + ) +endif # MICROPY_PY_OPENAMP_REMOTEPROC + # Disable compiler warnings in OpenAMP (variables used only for assert). $(BUILD)/$(OPENAMP_DIR)/lib/rpmsg/rpmsg_virtio.o: CFLAGS += -Wno-unused-but-set-variable $(BUILD)/$(OPENAMP_DIR)/lib/virtio_mmio/virtio_mmio_drv.o: CFLAGS += -Wno-unused-but-set-variable diff --git a/extmod/modopenamp.c b/extmod/modopenamp.c index b0c834c281..eb19c4b737 100644 --- a/extmod/modopenamp.c +++ b/extmod/modopenamp.c @@ -77,6 +77,10 @@ static const char openamp_trace_buf[128]; #define debug_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__) +#if MICROPY_PY_OPENAMP_REMOTEPROC +extern mp_obj_type_t openamp_remoteproc_type; +#endif + static struct metal_device shm_device = { .name = METAL_SHM_NAME, // The number of IO regions is fixed and must match the number and @@ -381,6 +385,9 @@ static const mp_rom_map_elem_t globals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_ENDPOINT_ADDR_ANY), MP_ROM_INT(RPMSG_ADDR_ANY) }, { MP_ROM_QSTR(MP_QSTR_new_service_callback), MP_ROM_PTR(&openamp_new_service_callback_obj) }, { MP_ROM_QSTR(MP_QSTR_Endpoint), MP_ROM_PTR(&endpoint_type) }, + #if MICROPY_PY_OPENAMP_REMOTEPROC + { MP_ROM_QSTR(MP_QSTR_RemoteProc), MP_ROM_PTR(&openamp_remoteproc_type) }, + #endif }; static MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); diff --git a/extmod/modopenamp.h b/extmod/modopenamp.h index 750f689297..8f677788f9 100644 --- a/extmod/modopenamp.h +++ b/extmod/modopenamp.h @@ -47,6 +47,18 @@ #define MICROPY_PY_OPENAMP_TRACE_BUF_ENABLE (1) #endif +// For ports that don't define a custom image store, this enables a generic +// VFS-based image store that supports loading elf files from storage. +#ifndef MICROPY_PY_OPENAMP_REMOTEPROC_STORE_ENABLE +#define MICROPY_PY_OPENAMP_REMOTEPROC_STORE_ENABLE (1) +#endif + +// Enable or disable support for loading elf files. This option saves +// around 7KBs when disabled. +#ifndef MICROPY_PY_OPENAMP_REMOTEPROC_ELFLD_ENABLE +#define MICROPY_PY_OPENAMP_REMOTEPROC_ELFLD_ENABLE (1) +#endif + // The resource table is used for sharing the configuration of the virtio // device, vrings and other resources, between the host and remote cores. // The layout and address the table structure must match the one used in diff --git a/extmod/modopenamp_remoteproc.c b/extmod/modopenamp_remoteproc.c new file mode 100644 index 0000000000..6f43c71546 --- /dev/null +++ b/extmod/modopenamp_remoteproc.c @@ -0,0 +1,175 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023-2024 Arduino SA + * + * 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. + * + * OpenAMP's remoteproc class. + */ + +#if MICROPY_PY_OPENAMP_REMOTEPROC + +#include "py/obj.h" +#include "py/nlr.h" +#include "py/runtime.h" +#include "py/stream.h" +#include "extmod/vfs.h" + +#include "metal/sys.h" +#include "metal/alloc.h" +#include "metal/errno.h" +#include "metal/io.h" + +#include "openamp/open_amp.h" +#include "openamp/remoteproc.h" +#include "openamp/remoteproc_loader.h" + +#include "modopenamp.h" +#include "modopenamp_remoteproc.h" + +#define DEBUG_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__) + +#if !MICROPY_PY_OPENAMP +#error "MICROPY_PY_OPENAMP_REMOTEPROC requires MICROPY_PY_OPENAMP" +#endif + +typedef struct openamp_remoteproc_obj { + mp_obj_base_t base; + struct remoteproc rproc; +} openamp_remoteproc_obj_t; + +const mp_obj_type_t openamp_remoteproc_type; + +// Port-defined image store operations. +extern struct image_store_ops openamp_remoteproc_store_ops; + +// Port-defined remote-proc operations. +const struct remoteproc_ops openamp_remoteproc_ops = { + .init = mp_openamp_remoteproc_init, + .mmap = mp_openamp_remoteproc_mmap, + .start = mp_openamp_remoteproc_start, + .stop = mp_openamp_remoteproc_stop, + .config = mp_openamp_remoteproc_config, + .remove = mp_openamp_remoteproc_remove, + .shutdown = mp_openamp_remoteproc_shutdown, +}; + +static mp_obj_t openamp_remoteproc_start(mp_obj_t self_in) { + openamp_remoteproc_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // Start the processor to run the application. + int error = remoteproc_start(&self->rproc); + if (error != 0) { + self->rproc.state = RPROC_ERROR; + mp_raise_OSError(error); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(openamp_remoteproc_start_obj, openamp_remoteproc_start); + +static mp_obj_t openamp_remoteproc_stop(mp_obj_t self_in) { + openamp_remoteproc_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // Stop the processor, but the processor is not powered down. + int error = remoteproc_stop(&self->rproc); + if (error != 0) { + mp_raise_OSError(error); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(openamp_remoteproc_stop_obj, openamp_remoteproc_stop); + +static mp_obj_t openamp_remoteproc_shutdown(mp_obj_t self_in) { + openamp_remoteproc_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // Shutdown the remoteproc and release its resources. + int error = remoteproc_shutdown(&self->rproc); + if (error != 0) { + mp_raise_OSError(error); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(openamp_remoteproc_shutdown_obj, openamp_remoteproc_shutdown); + +mp_obj_t openamp_remoteproc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_entry }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_entry, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_NONE } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + openamp_remoteproc_obj_t *self = mp_obj_malloc_with_finaliser(openamp_remoteproc_obj_t, &openamp_remoteproc_type); + + // Make sure OpenAMP is initialized. + if (MP_STATE_PORT(virtio_device) == NULL) { + openamp_init(); + } + + // Create a remoteproc instance. + // NOTE: ports should use rproc->priv to allocate the image store, + // which gets passed to remoteproc_load(), and all of the store ops. + remoteproc_init(&self->rproc, &openamp_remoteproc_ops, NULL); + + // Configure the remote before loading applications (optional). + int error = remoteproc_config(&self->rproc, NULL); + if (error != 0) { + mp_raise_OSError(error); + } + + if (mp_obj_is_int(args[ARG_entry].u_obj)) { + self->rproc.bootaddr = mp_obj_get_int(args[ARG_entry].u_obj); + } else { + #if MICROPY_PY_OPENAMP_REMOTEPROC_ELFLD_ENABLE + // Load firmware. + const char *path = mp_obj_str_get_str(args[ARG_entry].u_obj); + int error = remoteproc_load(&self->rproc, path, self->rproc.priv, &openamp_remoteproc_store_ops, NULL); + if (error != 0) { + mp_raise_OSError(error); + } + #else + mp_raise_TypeError(MP_ERROR_TEXT("loading firmware is not supported.")); + #endif + } + return MP_OBJ_FROM_PTR(self); +} + +static const mp_rom_map_elem_t openamp_remoteproc_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_RemoteProc) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&openamp_remoteproc_shutdown_obj) }, + { MP_ROM_QSTR(MP_QSTR_start), MP_ROM_PTR(&openamp_remoteproc_start_obj) }, + { MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&openamp_remoteproc_stop_obj) }, + { MP_ROM_QSTR(MP_QSTR_shutdown), MP_ROM_PTR(&openamp_remoteproc_shutdown_obj) }, +}; +static MP_DEFINE_CONST_DICT(openamp_remoteproc_dict, openamp_remoteproc_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + openamp_remoteproc_type, + MP_QSTR_RemoteProc, + MP_TYPE_FLAG_NONE, + make_new, openamp_remoteproc_make_new, + locals_dict, &openamp_remoteproc_dict + ); + +#endif // MICROPY_PY_OPENAMP_REMOTEPROC diff --git a/extmod/modopenamp_remoteproc.h b/extmod/modopenamp_remoteproc.h new file mode 100644 index 0000000000..9bc2b07064 --- /dev/null +++ b/extmod/modopenamp_remoteproc.h @@ -0,0 +1,46 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023-2024 Arduino SA + * + * 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. + * + * OpenAMP's remoteproc class. + */ +#ifndef MICROPY_INCLUDED_MODOPENAMP_REMOTEPROC_H +#define MICROPY_INCLUDED_MODOPENAMP_REMOTEPROC_H + +#include "openamp/remoteproc.h" +#include "openamp/remoteproc_loader.h" + +void *mp_openamp_remoteproc_store_alloc(void); +struct remoteproc *mp_openamp_remoteproc_init(struct remoteproc *rproc, + const struct remoteproc_ops *ops, void *arg); +void *mp_openamp_remoteproc_mmap(struct remoteproc *rproc, metal_phys_addr_t *pa, + metal_phys_addr_t *da, size_t size, unsigned int attribute, + struct metal_io_region **io); +int mp_openamp_remoteproc_start(struct remoteproc *rproc); +int mp_openamp_remoteproc_stop(struct remoteproc *rproc); +int mp_openamp_remoteproc_config(struct remoteproc *rproc, void *data); +void mp_openamp_remoteproc_remove(struct remoteproc *rproc); +int mp_openamp_remoteproc_shutdown(struct remoteproc *rproc); + +#endif // MICROPY_INCLUDED_MODOPENAMP_REMOTEPROC_H diff --git a/extmod/modopenamp_remoteproc_store.c b/extmod/modopenamp_remoteproc_store.c new file mode 100644 index 0000000000..857c133469 --- /dev/null +++ b/extmod/modopenamp_remoteproc_store.c @@ -0,0 +1,146 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023-2024 Arduino SA + * + * 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. + * + * OpenAMP's remoteproc store. + */ + +#if MICROPY_PY_OPENAMP_REMOTEPROC + +#include "py/obj.h" +#include "py/nlr.h" +#include "py/runtime.h" +#include "py/stream.h" +#include "extmod/vfs.h" + +#include "metal/sys.h" +#include "metal/alloc.h" +#include "metal/errno.h" +#include "metal/io.h" + +#include "openamp/remoteproc.h" +#include "openamp/remoteproc_loader.h" + +#include "modopenamp.h" +#include "modopenamp_remoteproc.h" + +#if MICROPY_PY_OPENAMP_REMOTEPROC_STORE_ENABLE + +#define DEBUG_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__) + +// Note the initial file buffer size needs to be at least 512 to read +// enough of the elf headers on the first call to store_open(), and on +// subsequent calls to store functions, it gets reallocated if needed. +#define RPROC_FILE_STORE_BUF_SIZE (1024) + +typedef struct openamp_remoteproc_filestore { + size_t len; + uint8_t *buf; + mp_obj_t file; +} openamp_remoteproc_filestore_t; + +void *mp_openamp_remoteproc_store_alloc(void) { + // Allocate an rproc filestore. + openamp_remoteproc_filestore_t *fstore; + fstore = metal_allocate_memory(sizeof(openamp_remoteproc_filestore_t)); + fstore->len = RPROC_FILE_STORE_BUF_SIZE; + fstore->buf = metal_allocate_memory(RPROC_FILE_STORE_BUF_SIZE); + return fstore; +} + +static int openamp_remoteproc_store_open(void *store, const char *path, const void **image_data) { + DEBUG_printf("store_open(): %s\n", path); + mp_obj_t args[2] = { + mp_obj_new_str(path, strlen(path)), + MP_OBJ_NEW_QSTR(MP_QSTR_rb), + }; + + openamp_remoteproc_filestore_t *fstore = store; + fstore->file = mp_vfs_open(MP_ARRAY_SIZE(args), args, (mp_map_t *)&mp_const_empty_map); + + int error = 0; + mp_uint_t bytes = mp_stream_read_exactly(fstore->file, fstore->buf, RPROC_FILE_STORE_BUF_SIZE, &error); + if (error != 0 || bytes != RPROC_FILE_STORE_BUF_SIZE) { + return -EINVAL; + } + *image_data = fstore->buf; + return bytes; +} + +static void openamp_remoteproc_store_close(void *store) { + DEBUG_printf("store_close()\n"); + openamp_remoteproc_filestore_t *fstore = store; + mp_stream_close(fstore->file); + metal_free_memory(fstore->buf); + metal_free_memory(fstore); +} + +static int openamp_remoteproc_store_load(void *store, size_t offset, size_t size, + const void **data, metal_phys_addr_t pa, + struct metal_io_region *io, + char is_blocking) { + + int error = 0; + openamp_remoteproc_filestore_t *fstore = store; + + if (mp_stream_seek(fstore->file, offset, MP_SEEK_SET, &error) == -1) { + return -EINVAL; + } + + if (pa == METAL_BAD_PHYS) { + if (size > fstore->len) { + // Note tracked allocs don't support realloc. + fstore->len = size; + fstore->buf = metal_allocate_memory(size); + DEBUG_printf("store_load() realloc to %lu\n", fstore->len); + } + *data = fstore->buf; + DEBUG_printf("store_load(): pa 0x%lx offset %u size %u \n", (uint32_t)pa, offset, size); + } else { + void *va = metal_io_phys_to_virt(io, pa); + if (va == NULL) { + return -EINVAL; + } + *data = va; + DEBUG_printf("store_load(): pa 0x%lx va 0x%p offset %u size %u \n", (uint32_t)pa, va, offset, size); + } + + mp_uint_t bytes = mp_stream_read_exactly(fstore->file, (void *)*data, size, &error); + if (bytes != size || error != 0) { + return -EINVAL; + } + + return bytes; +} + +const struct image_store_ops openamp_remoteproc_store_ops = { + .open = openamp_remoteproc_store_open, + .close = openamp_remoteproc_store_close, + .load = openamp_remoteproc_store_load, + .features = SUPPORT_SEEK, +}; + +#endif // MICROPY_PY_OPENAMP_REMOTEPROC_STORE_ENABLE + +#endif // MICROPY_PY_OPENAMP_REMOTEPROC From 13297d8c3a3cedbf5fd0d2817077270b284fe401 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 13 Nov 2023 17:24:41 +0100 Subject: [PATCH 22/49] stm32: Implement port backend for libmetal. Signed-off-by: iabdalkader --- ports/stm32/Makefile | 7 +++ ports/stm32/irq.h | 2 + ports/stm32/mpmetalport.c | 107 ++++++++++++++++++++++++++++++++++++++ ports/stm32/mpmetalport.h | 76 +++++++++++++++++++++++++++ 4 files changed, 192 insertions(+) create mode 100644 ports/stm32/mpmetalport.c create mode 100644 ports/stm32/mpmetalport.h diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index d1694426d3..7560903196 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -422,6 +422,13 @@ endif endif # MICROPY_PY_BLUETOOTH +# Add stm32-specific implementation of libmetal (and optionally OpenAMP's rproc). +# Note: libmetal code is generated via a pre-processor so ensure that runs first. +ifeq ($(MICROPY_PY_OPENAMP),1) +SRC_C += mpmetalport.c +$(BUILD)/mpmetalport.o: $(BUILD)/openamp/metal/config.h +endif + # SRC_O should be placed first to work around this LTO bug with binutils <2.35: # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=83967 OBJ += $(addprefix $(BUILD)/, $(SRC_O)) diff --git a/ports/stm32/irq.h b/ports/stm32/irq.h index e3a204ec96..58e6d0a804 100644 --- a/ports/stm32/irq.h +++ b/ports/stm32/irq.h @@ -174,6 +174,8 @@ static inline void restore_irq_pri(uint32_t state) { #define IRQ_PRI_SPI NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 8, 0) +#define IRQ_PRI_HSEM NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 10, 0) + // Interrupt priority for non-special timers. #define IRQ_PRI_TIMX NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 13, 0) diff --git a/ports/stm32/mpmetalport.c b/ports/stm32/mpmetalport.c new file mode 100644 index 0000000000..72f5537ce5 --- /dev/null +++ b/ports/stm32/mpmetalport.c @@ -0,0 +1,107 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Arduino SA + * + * 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. + * + * libmetal stm32 port. + */ + +#include "py/mperrno.h" +#include "py/mphal.h" + +#include "mpu.h" + +#include "metal/sys.h" +#include "metal/utilities.h" +#include "metal/device.h" + +struct metal_state _metal; +static mp_sched_node_t rproc_notify_node; + +int metal_sys_init(const struct metal_init_params *params) { + metal_unused(params); + + // Clear HSEM pending IRQ. + HSEM_COMMON->ICR |= (uint32_t)__HAL_HSEM_SEMID_TO_MASK(METAL_HSEM_MASTER_ID); + HAL_NVIC_ClearPendingIRQ(HSEM1_IRQn); + + // Enable and configure HSEM. + __HAL_RCC_HSEM_CLK_ENABLE(); + NVIC_SetPriority(HSEM1_IRQn, IRQ_PRI_HSEM); + HAL_NVIC_EnableIRQ(HSEM1_IRQn); + HAL_HSEM_ActivateNotification(__HAL_HSEM_SEMID_TO_MASK(METAL_HSEM_MASTER_ID)); + + #ifndef VIRTIO_USE_DCACHE + // If cache management is not enabled, configure the MPU to disable caching + // for the entire shared memory region. + uint32_t irq_state = mpu_config_start(); + mpu_config_region(MPU_REGION_OPENAMP, METAL_MPU_REGION_BASE, MPU_CONFIG_SHARED_UNCACHED(METAL_MPU_REGION_SIZE)); + mpu_config_end(irq_state); + #endif + + metal_bus_register(&metal_generic_bus); + return 0; +} + +void metal_sys_finish(void) { + HAL_NVIC_DisableIRQ(HSEM1_IRQn); + HAL_HSEM_DeactivateNotification(__HAL_HSEM_SEMID_TO_MASK(METAL_HSEM_MASTER_ID)); + __HAL_RCC_HSEM_CLK_DISABLE(); + metal_bus_unregister(&metal_generic_bus); +} + +unsigned int sys_irq_save_disable(void) { + return disable_irq(); +} + +void sys_irq_restore_enable(unsigned int state) { + enable_irq(state); +} + +void *metal_machine_io_mem_map(void *va, metal_phys_addr_t pa, + size_t size, unsigned int flags) { + metal_unused(pa); + metal_unused(size); + metal_unused(flags); + return va; +} + +void metal_machine_cache_flush(void *addr, unsigned int len) { + SCB_CleanDCache_by_Addr(addr, len); +} + +void metal_machine_cache_invalidate(void *addr, unsigned int len) { + SCB_InvalidateDCache_by_Addr(addr, len); +} + +int metal_rproc_notify(void *priv, uint32_t id) { + HAL_HSEM_FastTake(METAL_HSEM_REMOTE_ID); + HAL_HSEM_Release(METAL_HSEM_REMOTE_ID, 0); + return 0; +} + +void HSEM1_IRQHandler(void) { + HAL_HSEM_IRQHandler(); + mp_sched_schedule_node(&rproc_notify_node, openamp_remoteproc_notified); + HAL_HSEM_ActivateNotification(__HAL_HSEM_SEMID_TO_MASK(METAL_HSEM_MASTER_ID)); +} diff --git a/ports/stm32/mpmetalport.h b/ports/stm32/mpmetalport.h new file mode 100644 index 0000000000..53f329d15b --- /dev/null +++ b/ports/stm32/mpmetalport.h @@ -0,0 +1,76 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Arduino SA + * + * 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. + * + * libmetal stm32 port. + */ +#ifndef MICROPY_INCLUDED_STM32_MPMETALPORT_H +#define MICROPY_INCLUDED_STM32_MPMETALPORT_H + +#include +#include "py/mphal.h" +#include "py/runtime.h" + +#define METAL_HAVE_STDATOMIC_H 0 +#define METAL_HAVE_FUTEX_H 0 + +#define METAL_MAX_DEVICE_REGIONS 2 + +#define METAL_HSEM_REMOTE_ID 0 +#define METAL_HSEM_MASTER_ID 1 + +// Set to 1 to enable log output. +#define METAL_LOG_HANDLER_ENABLE 0 + +#define metal_cpu_yield() + +// Shared memory config +#define METAL_SHM_NAME "OPENAMP_SHM" +// Note 1K must be reserved at the start of the openamp +// shared memory region, for the shared resource table. +#define METAL_RSC_ADDR ((void *)_openamp_shm_region_start) +#define METAL_RSC_SIZE (1024) + +#define METAL_SHM_ADDR ((metal_phys_addr_t)(_openamp_shm_region_start + METAL_RSC_SIZE)) +#define METAL_SHM_SIZE ((size_t)(_openamp_shm_region_end - _openamp_shm_region_start - METAL_RSC_SIZE)) + +#define METAL_MPU_REGION_BASE ((uint32_t)_openamp_shm_region_start) +#define METAL_MPU_REGION_SIZE (MPU_REGION_SIZE_64KB) + +extern const char _openamp_shm_region_start[]; +extern const char _openamp_shm_region_end[]; + +int metal_rproc_notify(void *priv, uint32_t id); +extern void openamp_remoteproc_notified(mp_sched_node_t *node); + +static inline int __metal_sleep_usec(unsigned int usec) { + mp_hal_delay_us(usec); + return 0; +} + +static inline void metal_generic_default_poll(void) { + MICROPY_EVENT_POLL_HOOK +} + +#endif // MICROPY_INCLUDED_STM32_METAL_PORT_H From 09eb4caccb34b16647a650ddd396bcdd318613dc Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 13 Nov 2023 17:25:11 +0100 Subject: [PATCH 23/49] stm32: Implement port backend for OpenAMP's remoteproc. Signed-off-by: iabdalkader --- ports/stm32/Makefile | 4 + ports/stm32/mpremoteprocport.c | 153 +++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 ports/stm32/mpremoteprocport.c diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 7560903196..50ac48e5a1 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -427,6 +427,10 @@ endif # MICROPY_PY_BLUETOOTH ifeq ($(MICROPY_PY_OPENAMP),1) SRC_C += mpmetalport.c $(BUILD)/mpmetalport.o: $(BUILD)/openamp/metal/config.h +ifeq ($(MICROPY_PY_OPENAMP_REMOTEPROC),1) +SRC_C += mpremoteprocport.c +$(BUILD)/mpremoteprocport.o: $(BUILD)/openamp/metal/config.h +endif endif # SRC_O should be placed first to work around this LTO bug with binutils <2.35: diff --git a/ports/stm32/mpremoteprocport.c b/ports/stm32/mpremoteprocport.c new file mode 100644 index 0000000000..56dff43da4 --- /dev/null +++ b/ports/stm32/mpremoteprocport.c @@ -0,0 +1,153 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023-2024 Arduino SA + * + * 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. + * + * modremoteproc stm32 port. + */ + +#include +#include + +#include "py/obj.h" +#include "py/runtime.h" + +#include "metal/alloc.h" +#include "metal/errno.h" +#include "metal/io.h" +#include "metal/sys.h" +#include "metal/device.h" +#include "metal/utilities.h" +#include "extmod/modopenamp_remoteproc.h" + +#define DEBUG_printf(...) // mp_printf(&mp_plat_print, __VA_ARGS__) + +struct remoteproc *mp_openamp_remoteproc_init(struct remoteproc *rproc, + const struct remoteproc_ops *ops, void *arg) { + DEBUG_printf("rproc_init()\n"); + + rproc->ops = ops; + rproc->state = RPROC_OFFLINE; + // Allocate the image store and save it in private data. + rproc->priv = mp_openamp_remoteproc_store_alloc(); + return rproc; +} + +void *mp_openamp_remoteproc_mmap(struct remoteproc *rproc, metal_phys_addr_t *pa, + metal_phys_addr_t *da, size_t size, unsigned int attribute, + struct metal_io_region **io) { + DEBUG_printf("rproc_mmap(): pa 0x%p da 0x%p io 0x%p size %u\n", *pa, *da, *io, size); + + struct remoteproc_mem *mem; + metal_phys_addr_t lpa = *pa; + metal_phys_addr_t lda = *da; + + if (lda == METAL_BAD_PHYS) { + return NULL; + } + + if (lpa == METAL_BAD_PHYS) { + lpa = lda; + } + + // Currently this port doesn't support loading firmware to flash, + // only SD/SRAM images are supported. Check of load address is in + // the flash region, and if so return NULL. + if (lda >= FLASH_BASE && lda < FLASH_END) { + return NULL; + } + + mem = metal_allocate_memory(sizeof(*mem)); + if (!mem) { + return NULL; + } + + *io = metal_allocate_memory(sizeof(struct metal_io_region)); + if (!*io) { + metal_free_memory(mem); + return NULL; + } + + remoteproc_init_mem(mem, NULL, lpa, lda, size, *io); + + metal_io_init(*io, (void *)mem->da, &mem->pa, size, + sizeof(metal_phys_addr_t) << 3, attribute, NULL); + + remoteproc_add_mem(rproc, mem); + *pa = lpa; + *da = lda; + return metal_io_phys_to_virt(*io, mem->pa); +} + +int mp_openamp_remoteproc_start(struct remoteproc *rproc) { + DEBUG_printf("rproc_start()\n"); + if ((RCC->GCR & RCC_GCR_BOOT_C2) || (FLASH->OPTSR_CUR & FLASH_OPTSR_BCM4)) { + // The CM4 core has already been started manually, or auto-boot is enabled + // via the option bytes, in either case the core can't be restarted. + DEBUG_printf("rproc_start(): CM4 core is already booted.\n"); + return -1; + } + + // Flush M7 cache. + struct metal_list *node; + metal_list_for_each(&rproc->mems, node) { + struct remoteproc_mem *mem; + mem = metal_container_of(node, struct remoteproc_mem, node); + SCB_CleanDCache_by_Addr((uint32_t *)mem->pa, mem->size); + } + + HAL_SYSCFG_CM4BootAddConfig(SYSCFG_BOOT_ADDR0, (uint32_t)rproc->bootaddr); + HAL_RCCEx_EnableBootCore(RCC_BOOT_C2); + return 0; +} + +int mp_openamp_remoteproc_stop(struct remoteproc *rproc) { + DEBUG_printf("rproc_stop()\n"); + if (rproc->state == RPROC_RUNNING) { + // There's no straightforward way to reset or shut down + // the remote processor, so a full system reset is needed. + NVIC_SystemReset(); + } + return 0; +} + +int mp_openamp_remoteproc_config(struct remoteproc *rproc, void *data) { + DEBUG_printf("rproc_config()\n"); + (void)rproc; + return 0; +} + +void mp_openamp_remoteproc_remove(struct remoteproc *rproc) { + DEBUG_printf("rproc_remove()\n"); + (void)rproc; +} + +int mp_openamp_remoteproc_shutdown(struct remoteproc *rproc) { + DEBUG_printf("rproc_shutdown()\n"); + if (rproc->state == RPROC_RUNNING) { + // There's no straightforward way to reset or shut down + // the remote processor, so a full system reset is needed. + NVIC_SystemReset(); + } + return 0; +} From c859978da31bde0a43848490a4da339cdea22c2f Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 20 Nov 2023 10:03:22 +0100 Subject: [PATCH 24/49] stm32/boards/ARDUINO_GIGA: Enable OpenAMP. Signed-off-by: iabdalkader --- ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.mk | 2 ++ ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld | 12 ++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.mk b/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.mk index b927388c55..46fe40c2a6 100644 --- a/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.mk +++ b/ports/stm32/boards/ARDUINO_GIGA/mpconfigboard.mk @@ -23,6 +23,8 @@ MICROPY_PY_LWIP = 1 MICROPY_PY_NETWORK_CYW43 = 1 MICROPY_PY_SSL = 1 MICROPY_SSL_MBEDTLS = 1 +MICROPY_PY_OPENAMP = 1 +MICROPY_PY_OPENAMP_REMOTEPROC = 1 FROZEN_MANIFEST = $(BOARD_DIR)/manifest.py MBEDTLS_CONFIG_FILE = '"$(BOARD_DIR)/mbedtls_config_board.h"' diff --git a/ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld b/ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld index d5bd9598f9..793a76b970 100644 --- a/ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld +++ b/ports/stm32/boards/ARDUINO_GIGA/stm32h747.ld @@ -14,10 +14,10 @@ MEMORY SRAM4 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K /* SRAM4 D3 */ FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K /* Total available flash */ FLASH_EXT (rx) : ORIGIN = 0x90000000, LENGTH = 16384K /* 16MBs external QSPI flash */ - FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* sector 1 -> Flash storage */ - FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1792K /* Sector 0 -> Arduino Bootloader - Sector 1 -> Reserved for CM4/FS - Sectors 2 -> 15 firmware */ + FLASH_BL (rx) : ORIGIN = 0x08000000, LENGTH = 128K /* Arduino bootloader */ + FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* filesystem */ + FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1280K /* CM7 firmware */ + FLASH_CM4 (rx) : ORIGIN = 0x08180000, LENGTH = 512K /* CM4 firmware */ } /* produce a link error if there is not this amount of RAM for these sections */ @@ -44,4 +44,8 @@ _micropy_hw_internal_flash_storage_ram_cache_end = ORIGIN(DTCM) + LENGTH(DTCM); _micropy_hw_internal_flash_storage_start = ORIGIN(FLASH_FS); _micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); +/* OpenAMP shared memory region */ +_openamp_shm_region_start = ORIGIN(SRAM4); +_openamp_shm_region_end = ORIGIN(SRAM4) + LENGTH(SRAM4); + INCLUDE common_blifs.ld From fc9734363919c1ecba1f9ae58f8c093e47e55d56 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 11 Mar 2024 09:31:11 +0100 Subject: [PATCH 25/49] stm32/boards/ARDUINO_NICLA_VISION: Enable OpenAMP. Signed-off-by: iabdalkader --- .../boards/ARDUINO_NICLA_VISION/mpconfigboard.mk | 2 ++ ports/stm32/boards/ARDUINO_NICLA_VISION/stm32h747.ld | 12 ++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.mk b/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.mk index 2c48c17f38..77d547bce0 100644 --- a/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.mk +++ b/ports/stm32/boards/ARDUINO_NICLA_VISION/mpconfigboard.mk @@ -23,6 +23,8 @@ MICROPY_PY_LWIP = 1 MICROPY_PY_NETWORK_CYW43 = 1 MICROPY_PY_SSL = 1 MICROPY_SSL_MBEDTLS = 1 +MICROPY_PY_OPENAMP = 1 +MICROPY_PY_OPENAMP_REMOTEPROC = 1 FROZEN_MANIFEST = $(BOARD_DIR)/manifest.py MBEDTLS_CONFIG_FILE = '"$(BOARD_DIR)/mbedtls_config_board.h"' diff --git a/ports/stm32/boards/ARDUINO_NICLA_VISION/stm32h747.ld b/ports/stm32/boards/ARDUINO_NICLA_VISION/stm32h747.ld index fcbfc94d46..6d6ce279f2 100644 --- a/ports/stm32/boards/ARDUINO_NICLA_VISION/stm32h747.ld +++ b/ports/stm32/boards/ARDUINO_NICLA_VISION/stm32h747.ld @@ -14,10 +14,10 @@ MEMORY SRAM4 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K /* SRAM4 D3 */ FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K /* Total available flash */ FLASH_EXT (rx) : ORIGIN = 0x90000000, LENGTH = 16384K /* 16MBs external QSPI flash */ - FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* sector 1 -> Flash storage */ - FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1792K /* Sector 0 -> Arduino Bootloader - Sector 1 -> Reserved for CM4/FS - Sectors 2 -> 15 firmware */ + FLASH_BL (rx) : ORIGIN = 0x08000000, LENGTH = 128K /* Arduino bootloader */ + FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* filesystem */ + FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1280K /* CM7 firmware */ + FLASH_CM4 (rx) : ORIGIN = 0x08180000, LENGTH = 512K /* CM4 firmware */ } _cm4_ram_start = ORIGIN(SRAM4); @@ -46,4 +46,8 @@ _micropy_hw_internal_flash_storage_ram_cache_end = ORIGIN(DTCM) + LENGTH(DTCM); _micropy_hw_internal_flash_storage_start = ORIGIN(FLASH_FS); _micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); +/* OpenAMP shared memory region */ +_openamp_shm_region_start = ORIGIN(SRAM4); +_openamp_shm_region_end = ORIGIN(SRAM4) + LENGTH(SRAM4); + INCLUDE common_blifs.ld From 864e4596bf1c99112019b3d0d169f140ea9f2284 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Mon, 11 Mar 2024 09:32:10 +0100 Subject: [PATCH 26/49] stm32/boards/ARDUINO_PORTENTA_H7: Enable OpenAMP. Signed-off-by: iabdalkader --- .../boards/ARDUINO_PORTENTA_H7/mpconfigboard.mk | 2 ++ ports/stm32/boards/ARDUINO_PORTENTA_H7/stm32h747.ld | 12 ++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.mk b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.mk index cf4d40e5fe..c56c8f005f 100644 --- a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.mk +++ b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.mk @@ -23,6 +23,8 @@ MICROPY_PY_LWIP = 1 MICROPY_PY_NETWORK_CYW43 = 1 MICROPY_PY_SSL = 1 MICROPY_SSL_MBEDTLS = 1 +MICROPY_PY_OPENAMP = 1 +MICROPY_PY_OPENAMP_REMOTEPROC = 1 FROZEN_MANIFEST = $(BOARD_DIR)/manifest.py MBEDTLS_CONFIG_FILE = '"$(BOARD_DIR)/mbedtls_config_board.h"' diff --git a/ports/stm32/boards/ARDUINO_PORTENTA_H7/stm32h747.ld b/ports/stm32/boards/ARDUINO_PORTENTA_H7/stm32h747.ld index d5bd9598f9..793a76b970 100644 --- a/ports/stm32/boards/ARDUINO_PORTENTA_H7/stm32h747.ld +++ b/ports/stm32/boards/ARDUINO_PORTENTA_H7/stm32h747.ld @@ -14,10 +14,10 @@ MEMORY SRAM4 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K /* SRAM4 D3 */ FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K /* Total available flash */ FLASH_EXT (rx) : ORIGIN = 0x90000000, LENGTH = 16384K /* 16MBs external QSPI flash */ - FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* sector 1 -> Flash storage */ - FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1792K /* Sector 0 -> Arduino Bootloader - Sector 1 -> Reserved for CM4/FS - Sectors 2 -> 15 firmware */ + FLASH_BL (rx) : ORIGIN = 0x08000000, LENGTH = 128K /* Arduino bootloader */ + FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* filesystem */ + FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1280K /* CM7 firmware */ + FLASH_CM4 (rx) : ORIGIN = 0x08180000, LENGTH = 512K /* CM4 firmware */ } /* produce a link error if there is not this amount of RAM for these sections */ @@ -44,4 +44,8 @@ _micropy_hw_internal_flash_storage_ram_cache_end = ORIGIN(DTCM) + LENGTH(DTCM); _micropy_hw_internal_flash_storage_start = ORIGIN(FLASH_FS); _micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); +/* OpenAMP shared memory region */ +_openamp_shm_region_start = ORIGIN(SRAM4); +_openamp_shm_region_end = ORIGIN(SRAM4) + LENGTH(SRAM4); + INCLUDE common_blifs.ld From e5ca06a06fbb36b82dc50c118735cc1c10506080 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 29 Feb 2024 12:31:24 +0100 Subject: [PATCH 27/49] docs/library/openamp: Document the new openamp module. Signed-off-by: iabdalkader --- docs/library/index.rst | 1 + docs/library/openamp.rst | 115 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 docs/library/openamp.rst diff --git a/docs/library/index.rst b/docs/library/index.rst index eb29b76805..4209a0781a 100644 --- a/docs/library/index.rst +++ b/docs/library/index.rst @@ -103,6 +103,7 @@ the following libraries. micropython.rst neopixel.rst network.rst + openamp.rst uctypes.rst vfs.rst diff --git a/docs/library/openamp.rst b/docs/library/openamp.rst new file mode 100644 index 0000000000..de4d51ad06 --- /dev/null +++ b/docs/library/openamp.rst @@ -0,0 +1,115 @@ +:mod:`openamp` -- provides standard Asymmetric Multiprocessing (AMP) support +============================================================================ + +.. module:: openamp + :synopsis: provides standard Asymmetric Multiprocessing (AMP) support + +The ``openamp`` module provides a standard inter-processor communications infrastructure +for MicroPython. The module handles all of the details of OpenAMP, such as setting up +the shared resource table, initializing vrings, etc. It provides an API for using the +RPMsg bus infrastructure with the `Endpoint` class, and provides processor Life Cycle +Management (LCM) support, such as loading firmware and starting and stopping a remote +core, via the `RemoteProc` class. + +Example usage:: + + import openamp + + def ept_recv_callback(src, data): + print("Received message on endpoint", data) + + # Create a new RPMsg endpoint to communicate with the remote core. + ept = openamp.Endpoint("vuart-channel", callback=ept_recv_callback) + + # Create a RemoteProc object, load its firmware and start it. + rproc = openamp.RemoteProc("virtual_uart.elf") # Or entry point address (ex 0x081E0000) + rproc.start() + + while True: + if ept.is_ready(): + ept.send("data") + +Functions +--------- + +.. function:: new_service_callback(ns_callback) + + Set the new service callback. + + The *ns_callback* argument is a function that will be called when the remote processor + announces new services. At that point the host processor can choose to create the + announced endpoint, if this particular service is supported, or ignore it if it's + not. If this function is not set, the host processor should first register the + endpoint locally, and it will be automatically bound when the remote announces + the service. + +Endpoint class +-------------- + +.. class:: Endpoint(name, callback, src=ENDPOINT_ADDR_ANY, dest=ENDPOINT_ADDR_ANY) + + Construct a new RPMsg Endpoint. An endpoint is a bidirectional communication + channel between two cores. + + Arguments are: + + - *name* is the name of the endpoint. + - *callback* is a function that is called when the endpoint receives data with the + source address of the remote point, and the data as bytes passed by reference. + - *src* is the endpoint source address. If none is provided one will be assigned + to the endpoint by the library. + - *dest* is the endpoint destination address. If the endpoint is created from the + new_service_callback, this must be provided and it must match the remote endpoint's + source address. If the endpoint is registered locally, before the announcement, the + destination address will be assigned by the library when the endpoint is bound. + +.. method:: Endpoint.deinit() + + Destroy the endpoint and release all of its resources. + +.. method:: Endpoint.is_ready() + + Returns True if the endpoint is ready to send (i.e., has both a source and destination addresses) + +.. method:: Endpoint.send(src=-1, dest=-1, timeout=-1) + + Send a message to the remote processor over this endpoint. + + Arguments are: + + - *src* is the source endpoint address of the message. If none is provided, the + source address the endpoint is bound to is used. + - *dest* is the destination endpoint address of the message. If none is provided, + the destination address the endpoint is bound to is used. + - *timeout* specifies the time in milliseconds to wait for a free buffer. By default + the function is blocking. + +RemoteProc class +---------------- + +.. class:: RemoteProc(entry) + + The RemoteProc object provides processor Life Cycle Management (LCM) support, such as + loading firmware, starting and stopping a remote core. + + The *entry* argument can be a path to firmware image, in which case the firmware is + loaded from file to its target memory, or an entry point address, in which case the + firmware must be loaded already at the given address. + +.. method:: RemoteProc.start() + + Starts the remote processor. + +.. method:: RemoteProc.stop() + + Stops the remote processor. The exact behavior is platform-dependent. On the STM32H7 for + example it's not possible to stop and then restart the Cortex-M4 core, so a complete + system reset is performed on a call to this function. + +.. method:: RemoteProc.shutdown() + + Shutdown stops the remote processor and releases all of its resources. The exact behavior + is platform-dependent, however typically it disables power and clocks to the remote core. + This function is also used as the finaliser (i.e., called when ``RemoteProc`` object is + collected). Note that on the STM32H7, it's not possible to stop and then restart the + Cortex-M4 core, so a complete system reset is performed on a call to this function. From 486ca3a688542f9f535522c29342e2dafe805d5a Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 14 Mar 2024 08:41:52 +0100 Subject: [PATCH 28/49] tools/ci.sh: Add Arduino GIGA to stm32 CI build. Signed-off-by: iabdalkader --- .github/workflows/ports_stm32.yml | 1 + tools/ci.sh | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/.github/workflows/ports_stm32.yml b/.github/workflows/ports_stm32.yml index 84d30b27f6..f5e01dc1f6 100644 --- a/.github/workflows/ports_stm32.yml +++ b/.github/workflows/ports_stm32.yml @@ -25,6 +25,7 @@ jobs: ci_func: # names are functions in ci.sh - stm32_pyb_build - stm32_nucleo_build + - stm32_misc_build runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v4 diff --git a/tools/ci.sh b/tools/ci.sh index 3cbc51cfad..f94f23893b 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -366,6 +366,12 @@ function ci_stm32_nucleo_build { diff $BUILD_WB55/firmware.unpack.dfu $BUILD_WB55/firmware.unpack_no_sk.dfu } +function ci_stm32_misc_build { + make ${MAKEOPTS} -C mpy-cross + make ${MAKEOPTS} -C ports/stm32 BOARD=ARDUINO_GIGA submodules + make ${MAKEOPTS} -C ports/stm32 BOARD=ARDUINO_GIGA +} + ######################################################################################## # ports/unix From df2ff0c3553f67c3e8fe29f41013f27a75b608af Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 15 Mar 2024 17:51:47 +1100 Subject: [PATCH 29/49] LICENSE: Add libmetal and open-amp to 3rd-party license list. Signed-off-by: Damien George --- LICENSE | 2 ++ 1 file changed, 2 insertions(+) diff --git a/LICENSE b/LICENSE index b85f31808a..9142c46045 100644 --- a/LICENSE +++ b/LICENSE @@ -48,12 +48,14 @@ used during the build process and is not part of the compiled source code. /cmsis (BSD-3-clause) /crypto-algorithms (NONE) /libhydrogen (ISC) + /libmetal (BSD-3-clause) /littlefs (BSD-3-clause) /lwip (BSD-3-clause) /mynewt-nimble (Apache-2.0) /nrfx (BSD-3-clause) /nxp_driver (BSD-3-Clause) /oofatfs (BSD-1-clause) + /open-amp (BSD-3-clause) /pico-sdk (BSD-3-clause) /re15 (BSD-3-clause) /stm32lib (BSD-3-clause) From fb3820e3d62d284472420c68828a6b6443265b61 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 16 Mar 2024 00:40:39 +1100 Subject: [PATCH 30/49] nrf/boards: Enable MICROPY_HW_ENABLE_USBDEV on boards with USB CDC. These boards were broken by 9d0d262be069089f01da6b40d2cd78f1da14de0f. Signed-off-by: Damien George --- ports/nrf/boards/NRF52840_MDK_USB_DONGLE/mpconfigboard.h | 1 + ports/nrf/boards/PARTICLE_XENON/mpconfigboard.h | 1 + ports/nrf/boards/PCA10059/mpconfigboard.h | 1 + 3 files changed, 3 insertions(+) diff --git a/ports/nrf/boards/NRF52840_MDK_USB_DONGLE/mpconfigboard.h b/ports/nrf/boards/NRF52840_MDK_USB_DONGLE/mpconfigboard.h index 94624be028..499abbc4ef 100644 --- a/ports/nrf/boards/NRF52840_MDK_USB_DONGLE/mpconfigboard.h +++ b/ports/nrf/boards/NRF52840_MDK_USB_DONGLE/mpconfigboard.h @@ -37,6 +37,7 @@ #define MICROPY_HW_ENABLE_RNG (1) +#define MICROPY_HW_ENABLE_USBDEV (1) #define MICROPY_HW_USB_CDC (1) #define MICROPY_HW_HAS_LED (1) diff --git a/ports/nrf/boards/PARTICLE_XENON/mpconfigboard.h b/ports/nrf/boards/PARTICLE_XENON/mpconfigboard.h index 343fa4c2f3..35b70d6129 100644 --- a/ports/nrf/boards/PARTICLE_XENON/mpconfigboard.h +++ b/ports/nrf/boards/PARTICLE_XENON/mpconfigboard.h @@ -37,6 +37,7 @@ #define MICROPY_HW_ENABLE_RNG (1) +#define MICROPY_HW_ENABLE_USBDEV (1) #define MICROPY_HW_USB_CDC (1) #define MICROPY_HW_HAS_LED (1) diff --git a/ports/nrf/boards/PCA10059/mpconfigboard.h b/ports/nrf/boards/PCA10059/mpconfigboard.h index 1c87731584..97f6ccb941 100644 --- a/ports/nrf/boards/PCA10059/mpconfigboard.h +++ b/ports/nrf/boards/PCA10059/mpconfigboard.h @@ -37,6 +37,7 @@ #define MICROPY_HW_ENABLE_RNG (1) +#define MICROPY_HW_ENABLE_USBDEV (1) #define MICROPY_HW_USB_CDC (1) #define MICROPY_HW_HAS_LED (1) From 01c31ea804c7e2a034e1249b43c30673b4382116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20D=C3=B6rre?= Date: Sat, 24 Feb 2024 23:44:47 +0000 Subject: [PATCH 31/49] extmod/os_dupterm: Handle exception properly when it occurs in parallel. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When an exception is handled and the stream is closed, but while this happens, another exception occurs or dupterm is deactivated for another reason, the initial deactivation crashes, because its dupterm is removed. Co-authored-by: Damien George Signed-off-by: Felix Dörre --- extmod/os_dupterm.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extmod/os_dupterm.c b/extmod/os_dupterm.c index 156766a43c..399f2237fb 100644 --- a/extmod/os_dupterm.c +++ b/extmod/os_dupterm.c @@ -45,6 +45,10 @@ void mp_os_deactivate(size_t dupterm_idx, const char *msg, mp_obj_t exc) { if (exc != MP_OBJ_NULL) { mp_obj_print_exception(&mp_plat_print, exc); } + if (term == MP_OBJ_NULL) { + // Dupterm was already closed. + return; + } nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_stream_close(term); From 305707b281bc64aacb154e0165bd0a9ca3fedfe8 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 15 Mar 2024 18:39:23 +1100 Subject: [PATCH 32/49] lib/berkeley-db-1.xx: Update submodule URL and version. This updates the berkeley-db-1.xx submodule URL to a repository hosted under the micropython organisation, and makes the following changes: - Moves the berkeley-db header files to a single directory within the submodule, and references all these headers with a much fuller path, which prevents symbol clashes (eg with esp32 and queue.h). - Removes unused/non-working files from berkeley-db, which removes all symlinks in that repo (symlinks don't play well under Windows). - Allows injecting an external configuration header into berkeley-db, so the configuration doesn't have to be provided by -Dxx=yy flags to the compiler (and possibly clashing with other symbols). - Removes the advertising clause from the BSD 4-clause license of berkeley-db (see relevant commit and README.Impt.License.Change for details). Signed-off-by: Damien George --- .gitmodules | 2 +- lib/berkeley-db-1.xx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index aa9bb3d6d9..423d1bbeed 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,7 +9,7 @@ url = https://github.com/lwip-tcpip/lwip.git [submodule "lib/berkeley-db-1.xx"] path = lib/berkeley-db-1.xx - url = https://github.com/pfalcon/berkeley-db-1.xx + url = https://github.com/micropython/berkeley-db-1.xx [submodule "lib/stm32lib"] path = lib/stm32lib url = https://github.com/micropython/stm32lib diff --git a/lib/berkeley-db-1.xx b/lib/berkeley-db-1.xx index 35aaec4418..85373b548f 160000 --- a/lib/berkeley-db-1.xx +++ b/lib/berkeley-db-1.xx @@ -1 +1 @@ -Subproject commit 35aaec4418ad78628a3b935885dd189d41ce779b +Subproject commit 85373b548f1fb0119a463582570b44189dfb09ae From cd8eea2ae99542257a33b29c9dfdc02b63fc1bd6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 8 Mar 2024 22:43:52 +1100 Subject: [PATCH 33/49] all: Update extmod, ports, examples to build with new berkeley-db lib. This provides a MicroPython-specific berkeley-db configuration in extmod/berkeley-db/berkeley_db_config_port.h, and cleans up the include path for this library. Fixes issue #13092. Signed-off-by: Damien George --- examples/natmod/btree/Makefile | 7 ++++--- examples/natmod/btree/btree_c.c | 4 ++++ extmod/berkeley-db/berkeley_db_config_port.h | 16 ++++++++++++++++ extmod/extmod.cmake | 16 ++++++++-------- extmod/extmod.mk | 10 +++++----- extmod/modbtree.c | 4 ++-- ports/esp8266/Makefile | 2 +- 7 files changed, 40 insertions(+), 19 deletions(-) create mode 100644 extmod/berkeley-db/berkeley_db_config_port.h diff --git a/examples/natmod/btree/Makefile b/examples/natmod/btree/Makefile index d795102b4a..218ec15a2a 100644 --- a/examples/natmod/btree/Makefile +++ b/examples/natmod/btree/Makefile @@ -11,9 +11,10 @@ SRC = btree_c.c btree_py.py ARCH = x64 BTREE_DIR = $(MPY_DIR)/lib/berkeley-db-1.xx -BTREE_DEFS = -D__DBINTERFACE_PRIVATE=1 -Dmpool_error="(void)" -Dabort=abort_ "-Dvirt_fd_t=void*" $(BTREE_DEFS_EXTRA) -CFLAGS += -I$(BTREE_DIR)/PORT/include -CFLAGS += -Wno-old-style-definition -Wno-sign-compare -Wno-unused-parameter $(BTREE_DEFS) +BERKELEY_DB_CONFIG_FILE ?= \"extmod/berkeley-db/berkeley_db_config_port.h\" +CFLAGS += -I$(BTREE_DIR)/include +CFLAGS += -DBERKELEY_DB_CONFIG_FILE=$(BERKELEY_DB_CONFIG_FILE) +CFLAGS += -Wno-old-style-definition -Wno-sign-compare -Wno-unused-parameter SRC += $(addprefix $(realpath $(BTREE_DIR))/,\ btree/bt_close.c \ diff --git a/examples/natmod/btree/btree_c.c b/examples/natmod/btree/btree_c.c index 3c60e41b2d..bbf51c731f 100644 --- a/examples/natmod/btree/btree_c.c +++ b/examples/natmod/btree/btree_c.c @@ -39,6 +39,10 @@ void abort_(void) { nlr_raise(mp_obj_new_exception(mp_load_global(MP_QSTR_RuntimeError))); } +int puts(const char *s) { + return mp_printf(&mp_plat_print, "%s\n", s); +} + int native_errno; #if defined(__linux__) int *__errno_location (void) diff --git a/extmod/berkeley-db/berkeley_db_config_port.h b/extmod/berkeley-db/berkeley_db_config_port.h new file mode 100644 index 0000000000..41e4acd81e --- /dev/null +++ b/extmod/berkeley-db/berkeley_db_config_port.h @@ -0,0 +1,16 @@ +// Berkeley-db configuration. + +#define __DBINTERFACE_PRIVATE 1 +#define mpool_error printf +#define abort abort_ +#define virt_fd_t void* + +#ifdef MICROPY_BERKELEY_DB_DEFPSIZE +#define DEFPSIZE MICROPY_BERKELEY_DB_DEFPSIZE +#endif + +#ifdef MICROPY_BERKELEY_DB_MINCACHE +#define MINCACHE MICROPY_BERKELEY_DB_MINCACHE +#endif + +__attribute__((noreturn)) void abort_(void); diff --git a/extmod/extmod.cmake b/extmod/extmod.cmake index 957580d15d..60f1a23ad6 100644 --- a/extmod/extmod.cmake +++ b/extmod/extmod.cmake @@ -132,27 +132,27 @@ if(MICROPY_PY_BTREE) ) target_include_directories(micropy_extmod_btree PRIVATE - ${MICROPY_LIB_BERKELEY_DIR}/PORT/include + ${MICROPY_LIB_BERKELEY_DIR}/include ) + if(NOT BERKELEY_DB_CONFIG_FILE) + set(BERKELEY_DB_CONFIG_FILE "${MICROPY_DIR}/extmod/berkeley-db/berkeley_db_config_port.h") + endif() + target_compile_definitions(micropy_extmod_btree PRIVATE - __DBINTERFACE_PRIVATE=1 - mpool_error=printf - abort=abort_ - "virt_fd_t=void*" + BERKELEY_DB_CONFIG_FILE="${BERKELEY_DB_CONFIG_FILE}" ) # The include directories and compile definitions below are needed to build # modbtree.c and should be added to the main MicroPython target. list(APPEND MICROPY_INC_CORE - "${MICROPY_LIB_BERKELEY_DIR}/PORT/include" + "${MICROPY_LIB_BERKELEY_DIR}/include" ) list(APPEND MICROPY_DEF_CORE MICROPY_PY_BTREE=1 - __DBINTERFACE_PRIVATE=1 - "virt_fd_t=void*" + BERKELEY_DB_CONFIG_FILE="${BERKELEY_DB_CONFIG_FILE}" ) list(APPEND MICROPY_SOURCE_EXTMOD diff --git a/extmod/extmod.mk b/extmod/extmod.mk index bf686fbbb4..f7c6f9988e 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -381,8 +381,10 @@ endif ifeq ($(MICROPY_PY_BTREE),1) BTREE_DIR = lib/berkeley-db-1.xx -BTREE_DEFS = -D__DBINTERFACE_PRIVATE=1 -Dmpool_error=printf -Dabort=abort_ "-Dvirt_fd_t=void*" $(BTREE_DEFS_EXTRA) -INC += -I$(TOP)/$(BTREE_DIR)/PORT/include +BERKELEY_DB_CONFIG_FILE ?= \"extmod/berkeley-db/berkeley_db_config_port.h\" +CFLAGS_EXTMOD += -DBERKELEY_DB_CONFIG_FILE=$(BERKELEY_DB_CONFIG_FILE) +CFLAGS_EXTMOD += $(BTREE_DEFS_EXTRA) +INC += -I$(TOP)/$(BTREE_DIR)/include SRC_THIRDPARTY_C += $(addprefix $(BTREE_DIR)/,\ btree/bt_close.c \ btree/bt_conv.c \ @@ -401,9 +403,7 @@ SRC_THIRDPARTY_C += $(addprefix $(BTREE_DIR)/,\ ) CFLAGS_EXTMOD += -DMICROPY_PY_BTREE=1 # we need to suppress certain warnings to get berkeley-db to compile cleanly -# and we have separate BTREE_DEFS so the definitions don't interfere with other source code -$(BUILD)/$(BTREE_DIR)/%.o: CFLAGS += -Wno-old-style-definition -Wno-sign-compare -Wno-unused-parameter -Wno-deprecated-non-prototype -Wno-unknown-warning-option $(BTREE_DEFS) -$(BUILD)/extmod/modbtree.o: CFLAGS += $(BTREE_DEFS) +$(BUILD)/$(BTREE_DIR)/%.o: CFLAGS += -Wno-old-style-definition -Wno-sign-compare -Wno-unused-parameter -Wno-deprecated-non-prototype -Wno-unknown-warning-option endif ################################################################################ diff --git a/extmod/modbtree.c b/extmod/modbtree.c index f8c38e0ad4..55c13ac911 100644 --- a/extmod/modbtree.c +++ b/extmod/modbtree.c @@ -57,8 +57,8 @@ #undef CIRCLEQ_INSERT_TAIL #undef CIRCLEQ_REMOVE -#include -#include <../../btree/btree.h> +#include "berkeley-db/db.h" +#include "berkeley-db/btree.h" typedef struct _mp_obj_btree_t { mp_obj_base_t base; diff --git a/ports/esp8266/Makefile b/ports/esp8266/Makefile index 32eb39a203..8af7aba0aa 100644 --- a/ports/esp8266/Makefile +++ b/ports/esp8266/Makefile @@ -38,7 +38,7 @@ MICROPY_ROM_TEXT_COMPRESSION ?= 1 MICROPY_PY_SSL = 1 MICROPY_SSL_AXTLS = 1 AXTLS_DEFS_EXTRA = -Dabort=abort_ -DRT_MAX_PLAIN_LENGTH=1024 -DRT_EXTRA=4096 -BTREE_DEFS_EXTRA = -DDEFPSIZE=1024 -DMINCACHE=3 +BTREE_DEFS_EXTRA = -DMICROPY_BERKELEY_DB_DEFPSIZE=1024 -DMICROPY_BERKELEY_DB_MINCACHE=3 FROZEN_MANIFEST ?= boards/manifest.py From 7dff38fdc190d7b731fad8319d2ae8aa13fde18a Mon Sep 17 00:00:00 2001 From: Dash Peters Date: Sat, 11 Feb 2023 18:49:15 -0800 Subject: [PATCH 34/49] py/objdeque: Expand implementation to be doubly-ended and support iter. Add `pop()`, `appendleft()`, and `extend()` methods, support iteration and indexing, and initializing from an existing sequence. Iteration and indexing (subscription) have independent configuration flags to enable them. They are enabled by default at the same level that collections.deque is enabled (the extra features level). Also add tests for checking new behavior. Signed-off-by: Damien George --- ports/windows/mpconfigport.h | 2 + py/mpconfig.h | 10 ++ py/objdeque.c | 183 +++++++++++++++++++++++++++++++---- tests/basics/deque1.py | 49 ++++++++++ tests/basics/deque2.py | 66 +++++++++++-- tests/basics/deque2.py.exp | 15 ++- tests/basics/deque_slice.py | 29 ++++++ 7 files changed, 325 insertions(+), 29 deletions(-) create mode 100644 tests/basics/deque_slice.py diff --git a/ports/windows/mpconfigport.h b/ports/windows/mpconfigport.h index dee5568c62..55e44c6f5c 100644 --- a/ports/windows/mpconfigport.h +++ b/ports/windows/mpconfigport.h @@ -117,6 +117,8 @@ #define MICROPY_PY_SYS_STDFILES (1) #define MICROPY_PY_SYS_EXC_INFO (1) #define MICROPY_PY_COLLECTIONS_DEQUE (1) +#define MICROPY_PY_COLLECTIONS_DEQUE_ITER (1) +#define MICROPY_PY_COLLECTIONS_DEQUE_SUBSCR (1) #define MICROPY_PY_COLLECTIONS_ORDEREDDICT (1) #ifndef MICROPY_PY_MATH_SPECIAL_FUNCTIONS #define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (1) diff --git a/py/mpconfig.h b/py/mpconfig.h index 85b9e2c9cd..cb0f050a74 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1303,6 +1303,16 @@ typedef double mp_float_t; #define MICROPY_PY_COLLECTIONS_DEQUE (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif +// Whether "collections.deque" supports iteration +#ifndef MICROPY_PY_COLLECTIONS_DEQUE_ITER +#define MICROPY_PY_COLLECTIONS_DEQUE_ITER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) +#endif + +// Whether "collections.deque" supports subscription +#ifndef MICROPY_PY_COLLECTIONS_DEQUE_SUBSCR +#define MICROPY_PY_COLLECTIONS_DEQUE_SUBSCR (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) +#endif + // Whether to provide "collections.OrderedDict" type #ifndef MICROPY_PY_COLLECTIONS_ORDEREDDICT #define MICROPY_PY_COLLECTIONS_ORDEREDDICT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) diff --git a/py/objdeque.c b/py/objdeque.c index 68e1621793..583537017f 100644 --- a/py/objdeque.c +++ b/py/objdeque.c @@ -25,13 +25,11 @@ */ #include // for ssize_t -#include - -#include "py/mpconfig.h" -#if MICROPY_PY_COLLECTIONS_DEQUE #include "py/runtime.h" +#if MICROPY_PY_COLLECTIONS_DEQUE + typedef struct _mp_obj_deque_t { mp_obj_base_t base; size_t alloc; @@ -42,15 +40,15 @@ typedef struct _mp_obj_deque_t { #define FLAG_CHECK_OVERFLOW 1 } mp_obj_deque_t; +static mp_obj_t mp_obj_deque_append(mp_obj_t self_in, mp_obj_t arg); +static mp_obj_t mp_obj_deque_extend(mp_obj_t self_in, mp_obj_t arg_in); +#if MICROPY_PY_COLLECTIONS_DEQUE_ITER +static mp_obj_t mp_obj_new_deque_it(mp_obj_t deque, mp_obj_iter_buf_t *iter_buf); +#endif + static mp_obj_t deque_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, 2, 3, false); - /* Initialization from existing sequence is not supported, so an empty - tuple must be passed as such. */ - if (args[0] != mp_const_empty_tuple) { - mp_raise_ValueError(NULL); - } - // Protect against -1 leading to zero-length allocation and bad array access mp_int_t maxlen = mp_obj_get_int(args[1]); if (maxlen < 0) { @@ -66,21 +64,27 @@ static mp_obj_t deque_make_new(const mp_obj_type_t *type, size_t n_args, size_t o->flags = mp_obj_get_int(args[2]); } + mp_obj_deque_extend(MP_OBJ_FROM_PTR(o), args[0]); + return MP_OBJ_FROM_PTR(o); } +static size_t deque_len(mp_obj_deque_t *self) { + ssize_t len = self->i_put - self->i_get; + if (len < 0) { + len += self->alloc; + } + return len; +} + static mp_obj_t deque_unary_op(mp_unary_op_t op, mp_obj_t self_in) { mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in); switch (op) { case MP_UNARY_OP_BOOL: return mp_obj_new_bool(self->i_get != self->i_put); - case MP_UNARY_OP_LEN: { - ssize_t len = self->i_put - self->i_get; - if (len < 0) { - len += self->alloc; - } - return MP_OBJ_NEW_SMALL_INT(len); - } + case MP_UNARY_OP_LEN: + return MP_OBJ_NEW_SMALL_INT(deque_len(self)); + #if MICROPY_PY_SYS_GETSIZEOF case MP_UNARY_OP_SIZEOF: { size_t sz = sizeof(*self) + sizeof(mp_obj_t) * self->alloc; @@ -117,6 +121,45 @@ static mp_obj_t mp_obj_deque_append(mp_obj_t self_in, mp_obj_t arg) { } static MP_DEFINE_CONST_FUN_OBJ_2(deque_append_obj, mp_obj_deque_append); +static mp_obj_t mp_obj_deque_appendleft(mp_obj_t self_in, mp_obj_t arg) { + mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in); + + size_t new_i_get = self->i_get - 1; + if (self->i_get == 0) { + new_i_get = self->alloc - 1; + } + + if (self->flags & FLAG_CHECK_OVERFLOW && new_i_get == self->i_put) { + mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("full")); + } + + self->i_get = new_i_get; + self->items[self->i_get] = arg; + + // overwriting first element in deque + if (self->i_put == new_i_get) { + if (self->i_put == 0) { + self->i_put = self->alloc - 1; + } else { + self->i_put--; + } + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(deque_appendleft_obj, mp_obj_deque_appendleft); + +static mp_obj_t mp_obj_deque_extend(mp_obj_t self_in, mp_obj_t arg_in) { + mp_obj_iter_buf_t iter_buf; + mp_obj_t iter = mp_getiter(arg_in, &iter_buf); + mp_obj_t item; + while ((item = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) { + mp_obj_deque_append(self_in, item); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(deque_extend_obj, mp_obj_deque_extend); + static mp_obj_t deque_popleft(mp_obj_t self_in) { mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in); @@ -135,6 +178,51 @@ static mp_obj_t deque_popleft(mp_obj_t self_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(deque_popleft_obj, deque_popleft); +static mp_obj_t deque_pop(mp_obj_t self_in) { + mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in); + + if (self->i_get == self->i_put) { + mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("empty")); + } + + if (self->i_put == 0) { + self->i_put = self->alloc - 1; + } else { + self->i_put--; + } + + mp_obj_t ret = self->items[self->i_put]; + self->items[self->i_put] = MP_OBJ_NULL; + + return ret; +} +static MP_DEFINE_CONST_FUN_OBJ_1(deque_pop_obj, deque_pop); + +#if MICROPY_PY_COLLECTIONS_DEQUE_SUBSCR +static mp_obj_t deque_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { + if (value == MP_OBJ_NULL) { + // delete not supported, fall back to mp_obj_subscr() error message + return MP_OBJ_NULL; + } + mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in); + + size_t offset = mp_get_index(self->base.type, deque_len(self), index, false); + size_t index_val = self->i_get + offset; + if (index_val > self->alloc) { + index_val -= self->alloc; + } + + if (value == MP_OBJ_SENTINEL) { + // load + return self->items[index_val]; + } else { + // store into deque + self->items[index_val] = value; + return mp_const_none; + } +} +#endif + #if 0 static mp_obj_t deque_clear(mp_obj_t self_in) { mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in); @@ -147,21 +235,80 @@ static MP_DEFINE_CONST_FUN_OBJ_1(deque_clear_obj, deque_clear); static const mp_rom_map_elem_t deque_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_append), MP_ROM_PTR(&deque_append_obj) }, + { MP_ROM_QSTR(MP_QSTR_appendleft), MP_ROM_PTR(&deque_appendleft_obj) }, + { MP_ROM_QSTR(MP_QSTR_extend), MP_ROM_PTR(&deque_extend_obj) }, #if 0 { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&deque_clear_obj) }, #endif + { MP_ROM_QSTR(MP_QSTR_pop), MP_ROM_PTR(&deque_pop_obj) }, { MP_ROM_QSTR(MP_QSTR_popleft), MP_ROM_PTR(&deque_popleft_obj) }, }; static MP_DEFINE_CONST_DICT(deque_locals_dict, deque_locals_dict_table); +#if MICROPY_PY_COLLECTIONS_DEQUE_ITER +#define DEQUE_TYPE_FLAGS MP_TYPE_FLAG_ITER_IS_GETITER +#define DEQUE_TYPE_ITER iter, mp_obj_new_deque_it, +#else +#define DEQUE_TYPE_FLAGS MP_TYPE_FLAG_NONE +#define DEQUE_TYPE_ITER +#endif + +#if MICROPY_PY_COLLECTIONS_DEQUE_SUBSCR +#define DEQUE_TYPE_SUBSCR subscr, deque_subscr, +#else +#define DEQUE_TYPE_SUBSCR +#endif + MP_DEFINE_CONST_OBJ_TYPE( mp_type_deque, MP_QSTR_deque, - MP_TYPE_FLAG_NONE, + MP_TYPE_FLAG_ITER_IS_GETITER, make_new, deque_make_new, unary_op, deque_unary_op, + DEQUE_TYPE_SUBSCR + DEQUE_TYPE_ITER locals_dict, &deque_locals_dict ); +/******************************************************************************/ +/* deque iterator */ + +#if MICROPY_PY_COLLECTIONS_DEQUE_ITER + +typedef struct _mp_obj_deque_it_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + mp_obj_t deque; + size_t cur; +} mp_obj_deque_it_t; + +static mp_obj_t deque_it_iternext(mp_obj_t self_in) { + mp_obj_deque_it_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_deque_t *deque = MP_OBJ_TO_PTR(self->deque); + if (self->cur != deque->i_put) { + mp_obj_t o_out = deque->items[self->cur]; + if (++self->cur == deque->alloc) { + self->cur = 0; + } + return o_out; + } else { + return MP_OBJ_STOP_ITERATION; + } +} + +static mp_obj_t mp_obj_new_deque_it(mp_obj_t deque, mp_obj_iter_buf_t *iter_buf) { + mp_obj_deque_t *deque_ = MP_OBJ_TO_PTR(deque); + size_t i_get = deque_->i_get; + assert(sizeof(mp_obj_deque_it_t) <= sizeof(mp_obj_iter_buf_t)); + mp_obj_deque_it_t *o = (mp_obj_deque_it_t *)iter_buf; + o->base.type = &mp_type_polymorph_iter; + o->iternext = deque_it_iternext; + o->deque = deque; + o->cur = i_get; + return MP_OBJ_FROM_PTR(o); +} + +#endif + #endif // MICROPY_PY_COLLECTIONS_DEQUE diff --git a/tests/basics/deque1.py b/tests/basics/deque1.py index 8b7874e2b1..188069bcbf 100644 --- a/tests/basics/deque1.py +++ b/tests/basics/deque1.py @@ -63,3 +63,52 @@ try: ~d except TypeError: print("TypeError") + + +# Same tests, but now with pop() and appendleft() + +d = deque((), 2) +print(len(d)) +print(bool(d)) + +try: + d.popleft() +except IndexError: + print("IndexError") + +print(d.append(1)) +print(len(d)) +print(bool(d)) +print(d.popleft()) +print(len(d)) + +d.append(2) +print(d.popleft()) + +d.append(3) +d.append(4) +print(len(d)) +print(d.popleft(), d.popleft()) +try: + d.popleft() +except IndexError: + print("IndexError") + +d.append(5) +d.append(6) +d.append(7) +print(len(d)) +print(d.popleft(), d.popleft()) +print(len(d)) +try: + d.popleft() +except IndexError: + print("IndexError") + +d = deque((), 2) +d.appendleft(1) +d.appendleft(2) +d.appendleft(3) +d.appendleft(4) +d.appendleft(5) +print(d.pop(), d.pop()) \ No newline at end of file diff --git a/tests/basics/deque2.py b/tests/basics/deque2.py index 80fcd66785..743c040789 100644 --- a/tests/basics/deque2.py +++ b/tests/basics/deque2.py @@ -6,18 +6,50 @@ except ImportError: print("SKIP") raise SystemExit +# Initial sequence is supported +d = deque([1, 2, 3], 10) -# Initial sequence is not supported -try: - deque([1, 2, 3], 10) -except ValueError: - print("ValueError") +# iteration over sequence +for x in d: + print(x) -# Not even empty list, only empty tuple +# Iterables larger than maxlen have the beginnings removed, also tests +# iteration through conversion to list +d = deque([1, 2, 3, 4, 5], 3) +print(list(d)) + +# Empty iterables are also supported +deque([], 10) + +# Extending deques with iterables +d.extend([6, 7]) +print(list(d)) + +# Accessing queue elements via index +d = deque((0, 1, 2, 3), 5) +print(d[0], d[1], d[-1]) + +# Writing queue elements via index +d[3] = 5 +print(d[3]) + +# Accessing indices out of bounds raises IndexError try: - deque([], 10) -except ValueError: - print("ValueError") + d[4] +except IndexError: + print("IndexError") + +try: + d[4] = 0 +except IndexError: + print("IndexError") + +# Removing elements with del is not supported, fall back on mp_obj_subscr() error message +try: + del d[0] +except TypeError: + print("TypeError") + # Only fixed-size deques are supported, so length arg is mandatory try: @@ -32,6 +64,11 @@ try: except IndexError: print("IndexError") +try: + d.pop() +except IndexError: + print("IndexError") + print(d.append(1)) print(d.popleft()) @@ -46,6 +83,11 @@ try: except IndexError as e: print(repr(e)) +try: + d.pop() +except IndexError as e: + print(repr(e)) + d.append(5) d.append(6) print(len(d)) @@ -53,6 +95,12 @@ try: d.append(7) except IndexError as e: print(repr(e)) + +try: + d.appendleft(8) +except IndexError as e: + print(repr(e)) + print(len(d)) print(d.popleft(), d.popleft()) diff --git a/tests/basics/deque2.py.exp b/tests/basics/deque2.py.exp index 3df8acf405..71f74ed6c4 100644 --- a/tests/basics/deque2.py.exp +++ b/tests/basics/deque2.py.exp @@ -1,14 +1,25 @@ -ValueError -ValueError +1 +2 +3 +[3, 4, 5] +[5, 6, 7] +0 1 3 +5 +IndexError +IndexError TypeError +TypeError +IndexError IndexError None 1 2 3 4 IndexError('empty',) +IndexError('empty',) 2 IndexError('full',) +IndexError('full',) 2 5 6 0 diff --git a/tests/basics/deque_slice.py b/tests/basics/deque_slice.py new file mode 100644 index 0000000000..367aeea3a1 --- /dev/null +++ b/tests/basics/deque_slice.py @@ -0,0 +1,29 @@ +try: + from collections import deque +except ImportError: + print("SKIP") + raise SystemExit + +d = deque((), 10) + +d.append(1) +d.append(2) +d.append(3) + +# Index slicing for reads is not supported in CPython +try: + d[0:1] +except TypeError: + print("TypeError") + +# Index slicing for writes is not supported in CPython +try: + d[0:1] = (-1, -2) +except TypeError: + print("TypeError") + +# Index slicing for writes is not supported in CPython +try: + del d[0:1] +except TypeError: + print("TypeError") From bf18ddd98930787c22b561884e0b3807b47d412b Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 19 Mar 2024 10:04:29 +1100 Subject: [PATCH 35/49] tools/mpy-tool.py: Fix merging of more than 128 mpy files. The argument to MP_BC_MAKE_FUNCTION (raw code index) was being encoded as a byte instead of a variable unsigned int. That meant that if there were more than 128 merged mpy files the encoding would be invalid. Fix that by using `mp_encode_uint(idx)` to encode the raw code index. And also use `Opcode` constants for the opcode values to make it easier to understand the code. Signed-off-by: Damien George --- tools/mpy-tool.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tools/mpy-tool.py b/tools/mpy-tool.py index 35c06a3028..06ccd6a1da 100755 --- a/tools/mpy-tool.py +++ b/tools/mpy-tool.py @@ -1730,10 +1730,13 @@ def merge_mpy(compiled_modules, output_file): bytecode.append(0b00000010) # prelude size (n_info=1, n_cell=0) bytecode.extend(b"\x00") # simple_name: qstr index 0 (will use source filename) for idx in range(len(compiled_modules)): - bytecode.append(0x32) # MP_BC_MAKE_FUNCTION - bytecode.append(idx) # index raw code - bytecode.extend(b"\x34\x00\x59") # MP_BC_CALL_FUNCTION, 0 args, MP_BC_POP_TOP - bytecode.extend(b"\x51\x63") # MP_BC_LOAD_NONE, MP_BC_RETURN_VALUE + bytecode.append(Opcode.MP_BC_MAKE_FUNCTION) + bytecode.extend(mp_encode_uint(idx)) # index of raw code + bytecode.append(Opcode.MP_BC_CALL_FUNCTION) + bytecode.append(0) # 0 arguments + bytecode.append(Opcode.MP_BC_POP_TOP) + bytecode.append(Opcode.MP_BC_LOAD_CONST_NONE) + bytecode.append(Opcode.MP_BC_RETURN_VALUE) merged_mpy.extend(mp_encode_uint(len(bytecode) << 3 | 1 << 2)) # length, has_children merged_mpy.extend(bytecode) From c9016b4979a718c7a66b2145e00fbdabfa2dc509 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 18 Mar 2024 22:41:28 +1100 Subject: [PATCH 36/49] tests/basics: Split MicroPython-specific deque tests to separate file. So that the MicroPython-specific behaviour can be isolated, and the CPython compatible test don't need a .exp file. Signed-off-by: Damien George --- tests/basics/deque2.py | 68 ----------------- tests/basics/deque_micropython.py | 75 +++++++++++++++++++ ...deque2.py.exp => deque_micropython.py.exp} | 11 +-- 3 files changed, 76 insertions(+), 78 deletions(-) create mode 100644 tests/basics/deque_micropython.py rename tests/basics/{deque2.py.exp => deque_micropython.py.exp} (75%) diff --git a/tests/basics/deque2.py b/tests/basics/deque2.py index 743c040789..3552d5be37 100644 --- a/tests/basics/deque2.py +++ b/tests/basics/deque2.py @@ -1,5 +1,3 @@ -# Tests for deques with "check overflow" flag and other extensions -# wrt to CPython. try: from collections import deque except ImportError: @@ -43,69 +41,3 @@ try: d[4] = 0 except IndexError: print("IndexError") - -# Removing elements with del is not supported, fall back on mp_obj_subscr() error message -try: - del d[0] -except TypeError: - print("TypeError") - - -# Only fixed-size deques are supported, so length arg is mandatory -try: - deque(()) -except TypeError: - print("TypeError") - -d = deque((), 2, True) - -try: - d.popleft() -except IndexError: - print("IndexError") - -try: - d.pop() -except IndexError: - print("IndexError") - -print(d.append(1)) -print(d.popleft()) - -d.append(2) -print(d.popleft()) - -d.append(3) -d.append(4) -print(d.popleft(), d.popleft()) -try: - d.popleft() -except IndexError as e: - print(repr(e)) - -try: - d.pop() -except IndexError as e: - print(repr(e)) - -d.append(5) -d.append(6) -print(len(d)) -try: - d.append(7) -except IndexError as e: - print(repr(e)) - -try: - d.appendleft(8) -except IndexError as e: - print(repr(e)) - -print(len(d)) - -print(d.popleft(), d.popleft()) -print(len(d)) -try: - d.popleft() -except IndexError as e: - print(repr(e)) diff --git a/tests/basics/deque_micropython.py b/tests/basics/deque_micropython.py new file mode 100644 index 0000000000..5f32bbc496 --- /dev/null +++ b/tests/basics/deque_micropython.py @@ -0,0 +1,75 @@ +# Test MicroPython-specific features of collections.deque. + +try: + from collections import deque +except ImportError: + print("SKIP") + raise SystemExit + + +# Only fixed-size deques are supported, so length arg is mandatory. +try: + deque(()) +except TypeError: + print("TypeError") + +# Test third argument: flags when True means check for under/overflow +d = deque((), 2, True) + +try: + d.popleft() +except IndexError: + print("IndexError") + +try: + d.pop() +except IndexError: + print("IndexError") + +# Removing elements with del is not supported, fallback to +# mp_obj_subscr() error message. +try: + del d[0] +except TypeError: + print("TypeError") + +print(d.append(1)) +print(d.popleft()) + +d.append(2) +print(d.popleft()) + +d.append(3) +d.append(4) +print(d.popleft(), d.popleft()) +try: + d.popleft() +except IndexError as e: + print(repr(e)) + +try: + d.pop() +except IndexError as e: + print(repr(e)) + +d.append(5) +d.append(6) +print(len(d)) +try: + d.append(7) +except IndexError as e: + print(repr(e)) + +try: + d.appendleft(8) +except IndexError as e: + print(repr(e)) + +print(len(d)) + +print(d.popleft(), d.popleft()) +print(len(d)) +try: + d.popleft() +except IndexError as e: + print(repr(e)) diff --git a/tests/basics/deque2.py.exp b/tests/basics/deque_micropython.py.exp similarity index 75% rename from tests/basics/deque2.py.exp rename to tests/basics/deque_micropython.py.exp index 71f74ed6c4..f1ff7b77ac 100644 --- a/tests/basics/deque2.py.exp +++ b/tests/basics/deque_micropython.py.exp @@ -1,16 +1,7 @@ -1 -2 -3 -[3, 4, 5] -[5, 6, 7] -0 1 3 -5 -IndexError -IndexError -TypeError TypeError IndexError IndexError +TypeError None 1 2 From d92dff881ca99281893b1d77eee4bf2eca25d0c8 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 18 Mar 2024 22:42:33 +1100 Subject: [PATCH 37/49] docs/library/collections: Update deque docs to describe new features. Signed-off-by: Damien George --- docs/library/collections.rst | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/docs/library/collections.rst b/docs/library/collections.rst index 6cf2c096ff..6a23e456c6 100644 --- a/docs/library/collections.rst +++ b/docs/library/collections.rst @@ -18,7 +18,9 @@ Classes appends and pops from either side of the deque. New deques are created using the following arguments: - - *iterable* must be the empty tuple, and the new deque is created empty. + - *iterable* is an iterable used to populate the deque when it is + created. It can be an empty tuple or list to create a deque that + is initially empty. - *maxlen* must be specified and the deque will be bounded to this maximum length. Once the deque is full, any new items added will @@ -26,18 +28,37 @@ Classes - The optional *flags* can be 1 to check for overflow when adding items. - As well as supporting `bool` and `len`, deque objects have the following - methods: + Deque objects support `bool`, `len`, iteration and subscript load and store. + They also have the following methods: .. method:: deque.append(x) Add *x* to the right side of the deque. - Raises IndexError if overflow checking is enabled and there is no more room left. + Raises ``IndexError`` if overflow checking is enabled and there is + no more room in the queue. + + .. method:: deque.appendleft(x) + + Add *x* to the left side of the deque. + Raises ``IndexError`` if overflow checking is enabled and there is + no more room in the queue. + + .. method:: deque.pop() + + Remove and return an item from the right side of the deque. + Raises ``IndexError`` if no items are present. .. method:: deque.popleft() Remove and return an item from the left side of the deque. - Raises IndexError if no items are present. + Raises ``IndexError`` if no items are present. + + .. method:: deque.extend(iterable) + + Extend the deque by appending all the items from *iterable* to + the right of the deque. + Raises ``IndexError`` if overflow checking is enabled and there is + no more room in the deque. .. function:: namedtuple(name, fields) From f52b0d0ff1788225fd61a748e0786aa90e953481 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 18 Mar 2024 12:29:11 +1100 Subject: [PATCH 38/49] py/asm: Add ASM_NOT_REG and ASM_NEG_REG macros for unary ops. ASM_NOT_REG is optional, it can be synthesised by xor(reg, -1). ASM_NEG_REG can also be synthesised with a subtraction, but most architectures have a dedicated instruction for it. Signed-off-by: Damien George --- py/asmarm.c | 22 ++++++++++++++++++++++ py/asmarm.h | 4 ++++ py/asmthumb.h | 2 ++ py/asmx64.c | 10 ++++++++++ py/asmx64.h | 4 ++++ py/asmx86.c | 10 ++++++++++ py/asmx86.h | 4 ++++ py/asmxtensa.h | 5 +++++ 8 files changed, 61 insertions(+) diff --git a/py/asmarm.c b/py/asmarm.c index cd346949eb..6006490701 100644 --- a/py/asmarm.c +++ b/py/asmarm.c @@ -77,6 +77,11 @@ static uint asm_arm_op_mvn_imm(uint rd, uint imm) { return 0x3e00000 | (rd << 12) | imm; } +static uint asm_arm_op_mvn_reg(uint rd, uint rm) { + // mvn rd, rm + return 0x1e00000 | (rd << 12) | rm; +} + static uint asm_arm_op_add_imm(uint rd, uint rn, uint imm) { // add rd, rn, #imm return 0x2800000 | (rn << 16) | (rd << 12) | (imm & 0xFF); @@ -97,6 +102,11 @@ static uint asm_arm_op_sub_reg(uint rd, uint rn, uint rm) { return 0x0400000 | (rn << 16) | (rd << 12) | rm; } +static uint asm_arm_op_rsb_imm(uint rd, uint rn, uint imm) { + // rsb rd, rn, #imm + return 0x2600000 | (rn << 16) | (rd << 12) | (imm & 0xFF); +} + static uint asm_arm_op_mul_reg(uint rd, uint rm, uint rs) { // mul rd, rm, rs assert(rd != rm); @@ -228,11 +238,23 @@ void asm_arm_setcc_reg(asm_arm_t *as, uint rd, uint cond) { emit(as, asm_arm_op_mov_imm(rd, 0) | (cond ^ (1 << 28))); // mov!COND rd, #0 } +void asm_arm_mvn_reg_reg(asm_arm_t *as, uint rd, uint rm) { + // mvn rd, rm + // computes: rd := ~rm + emit_al(as, asm_arm_op_mvn_reg(rd, rm)); +} + void asm_arm_add_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm) { // add rd, rn, rm emit_al(as, asm_arm_op_add_reg(rd, rn, rm)); } +void asm_arm_rsb_reg_reg_imm(asm_arm_t *as, uint rd, uint rn, uint imm) { + // rsb rd, rn, #imm + // computes: rd := #imm - rn + emit_al(as, asm_arm_op_rsb_imm(rd, rn, imm)); +} + void asm_arm_sub_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm) { // sub rd, rn, rm emit_al(as, asm_arm_op_sub_reg(rd, rn, rm)); diff --git a/py/asmarm.h b/py/asmarm.h index 81c3f7b1cf..4a4253aef6 100644 --- a/py/asmarm.h +++ b/py/asmarm.h @@ -94,8 +94,10 @@ void asm_arm_cmp_reg_i8(asm_arm_t *as, uint rd, int imm); void asm_arm_cmp_reg_reg(asm_arm_t *as, uint rd, uint rn); // arithmetic +void asm_arm_mvn_reg_reg(asm_arm_t *as, uint rd, uint rm); void asm_arm_add_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm); void asm_arm_sub_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm); +void asm_arm_rsb_reg_reg_imm(asm_arm_t *as, uint rd, uint rn, uint imm); void asm_arm_mul_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm); void asm_arm_and_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm); void asm_arm_eor_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm); @@ -188,6 +190,8 @@ void asm_arm_bx_reg(asm_arm_t *as, uint reg_src); #define ASM_MOV_REG_LOCAL_ADDR(as, reg_dest, local_num) asm_arm_mov_reg_local_addr((as), (reg_dest), (local_num)) #define ASM_MOV_REG_PCREL(as, reg_dest, label) asm_arm_mov_reg_pcrel((as), (reg_dest), (label)) +#define ASM_NOT_REG(as, reg_dest) asm_arm_mvn_reg_reg((as), (reg_dest), (reg_dest)) +#define ASM_NEG_REG(as, reg_dest) asm_arm_rsb_reg_reg_imm((as), (reg_dest), (reg_dest), 0) #define ASM_LSL_REG_REG(as, reg_dest, reg_shift) asm_arm_lsl_reg_reg((as), (reg_dest), (reg_shift)) #define ASM_LSR_REG_REG(as, reg_dest, reg_shift) asm_arm_lsr_reg_reg((as), (reg_dest), (reg_shift)) #define ASM_ASR_REG_REG(as, reg_dest, reg_shift) asm_arm_asr_reg_reg((as), (reg_dest), (reg_shift)) diff --git a/py/asmthumb.h b/py/asmthumb.h index 2829ac4c7e..a9e68d7adb 100644 --- a/py/asmthumb.h +++ b/py/asmthumb.h @@ -406,6 +406,8 @@ void asm_thumb_b_rel12(asm_thumb_t *as, int rel); #define ASM_MOV_REG_LOCAL_ADDR(as, reg_dest, local_num) asm_thumb_mov_reg_local_addr((as), (reg_dest), (local_num)) #define ASM_MOV_REG_PCREL(as, rlo_dest, label) asm_thumb_mov_reg_pcrel((as), (rlo_dest), (label)) +#define ASM_NOT_REG(as, reg_dest) asm_thumb_mvn_rlo_rlo((as), (reg_dest), (reg_dest)) +#define ASM_NEG_REG(as, reg_dest) asm_thumb_neg_rlo_rlo((as), (reg_dest), (reg_dest)) #define ASM_LSL_REG_REG(as, reg_dest, reg_shift) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_LSL, (reg_dest), (reg_shift)) #define ASM_LSR_REG_REG(as, reg_dest, reg_shift) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_LSR, (reg_dest), (reg_shift)) #define ASM_ASR_REG_REG(as, reg_dest, reg_shift) asm_thumb_format_4((as), ASM_THUMB_FORMAT_4_ASR, (reg_dest), (reg_shift)) diff --git a/py/asmx64.c b/py/asmx64.c index abddc16269..d9f33cfb2a 100644 --- a/py/asmx64.c +++ b/py/asmx64.c @@ -54,6 +54,8 @@ #define OPCODE_MOVZX_RM8_TO_R64 (0xb6) /* 0x0f 0xb6/r */ #define OPCODE_MOVZX_RM16_TO_R64 (0xb7) /* 0x0f 0xb7/r */ #define OPCODE_LEA_MEM_TO_R64 (0x8d) /* /r */ +#define OPCODE_NOT_RM64 (0xf7) /* /2 */ +#define OPCODE_NEG_RM64 (0xf7) /* /3 */ #define OPCODE_AND_R64_TO_RM64 (0x21) /* /r */ #define OPCODE_OR_R64_TO_RM64 (0x09) /* /r */ #define OPCODE_XOR_R64_TO_RM64 (0x31) /* /r */ @@ -362,6 +364,14 @@ void asm_x64_mov_i64_to_r64_optimised(asm_x64_t *as, int64_t src_i64, int dest_r } } +void asm_x64_not_r64(asm_x64_t *as, int dest_r64) { + asm_x64_generic_r64_r64(as, dest_r64, 2, OPCODE_NOT_RM64); +} + +void asm_x64_neg_r64(asm_x64_t *as, int dest_r64) { + asm_x64_generic_r64_r64(as, dest_r64, 3, OPCODE_NEG_RM64); +} + void asm_x64_and_r64_r64(asm_x64_t *as, int dest_r64, int src_r64) { asm_x64_generic_r64_r64(as, dest_r64, src_r64, OPCODE_AND_R64_TO_RM64); } diff --git a/py/asmx64.h b/py/asmx64.h index a8efc2bf60..c63e31797e 100644 --- a/py/asmx64.h +++ b/py/asmx64.h @@ -97,6 +97,8 @@ void asm_x64_mov_mem8_to_r64zx(asm_x64_t *as, int src_r64, int src_disp, int des void asm_x64_mov_mem16_to_r64zx(asm_x64_t *as, int src_r64, int src_disp, int dest_r64); void asm_x64_mov_mem32_to_r64zx(asm_x64_t *as, int src_r64, int src_disp, int dest_r64); void asm_x64_mov_mem64_to_r64(asm_x64_t *as, int src_r64, int src_disp, int dest_r64); +void asm_x64_not_r64(asm_x64_t *as, int dest_r64); +void asm_x64_neg_r64(asm_x64_t *as, int dest_r64); void asm_x64_and_r64_r64(asm_x64_t *as, int dest_r64, int src_r64); void asm_x64_or_r64_r64(asm_x64_t *as, int dest_r64, int src_r64); void asm_x64_xor_r64_r64(asm_x64_t *as, int dest_r64, int src_r64); @@ -191,6 +193,8 @@ void asm_x64_call_ind(asm_x64_t *as, size_t fun_id, int temp_r32); #define ASM_MOV_REG_LOCAL_ADDR(as, reg_dest, local_num) asm_x64_mov_local_addr_to_r64((as), (local_num), (reg_dest)) #define ASM_MOV_REG_PCREL(as, reg_dest, label) asm_x64_mov_reg_pcrel((as), (reg_dest), (label)) +#define ASM_NOT_REG(as, reg) asm_x64_not_r64((as), (reg)) +#define ASM_NEG_REG(as, reg) asm_x64_neg_r64((as), (reg)) #define ASM_LSL_REG(as, reg) asm_x64_shl_r64_cl((as), (reg)) #define ASM_LSR_REG(as, reg) asm_x64_shr_r64_cl((as), (reg)) #define ASM_ASR_REG(as, reg) asm_x64_sar_r64_cl((as), (reg)) diff --git a/py/asmx86.c b/py/asmx86.c index 94e0213d65..4acac1b46a 100644 --- a/py/asmx86.c +++ b/py/asmx86.c @@ -54,6 +54,8 @@ #define OPCODE_MOVZX_RM8_TO_R32 (0xb6) /* 0x0f 0xb6/r */ #define OPCODE_MOVZX_RM16_TO_R32 (0xb7) /* 0x0f 0xb7/r */ #define OPCODE_LEA_MEM_TO_R32 (0x8d) /* /r */ +#define OPCODE_NOT_RM32 (0xf7) /* /2 */ +#define OPCODE_NEG_RM32 (0xf7) /* /3 */ #define OPCODE_AND_R32_TO_RM32 (0x21) /* /r */ #define OPCODE_OR_R32_TO_RM32 (0x09) /* /r */ #define OPCODE_XOR_R32_TO_RM32 (0x31) /* /r */ @@ -244,6 +246,14 @@ size_t asm_x86_mov_i32_to_r32(asm_x86_t *as, int32_t src_i32, int dest_r32) { return loc; } +void asm_x86_not_r32(asm_x86_t *as, int dest_r32) { + asm_x86_generic_r32_r32(as, dest_r32, 2, OPCODE_NOT_RM32); +} + +void asm_x86_neg_r32(asm_x86_t *as, int dest_r32) { + asm_x86_generic_r32_r32(as, dest_r32, 3, OPCODE_NEG_RM32); +} + void asm_x86_and_r32_r32(asm_x86_t *as, int dest_r32, int src_r32) { asm_x86_generic_r32_r32(as, dest_r32, src_r32, OPCODE_AND_R32_TO_RM32); } diff --git a/py/asmx86.h b/py/asmx86.h index ac98643a6a..027d44151e 100644 --- a/py/asmx86.h +++ b/py/asmx86.h @@ -92,6 +92,8 @@ void asm_x86_mov_r32_to_mem32(asm_x86_t *as, int src_r32, int dest_r32, int dest void asm_x86_mov_mem8_to_r32zx(asm_x86_t *as, int src_r32, int src_disp, int dest_r32); void asm_x86_mov_mem16_to_r32zx(asm_x86_t *as, int src_r32, int src_disp, int dest_r32); void asm_x86_mov_mem32_to_r32(asm_x86_t *as, int src_r32, int src_disp, int dest_r32); +void asm_x86_not_r32(asm_x86_t *as, int dest_r32); +void asm_x86_neg_r32(asm_x86_t *as, int dest_r32); void asm_x86_and_r32_r32(asm_x86_t *as, int dest_r32, int src_r32); void asm_x86_or_r32_r32(asm_x86_t *as, int dest_r32, int src_r32); void asm_x86_xor_r32_r32(asm_x86_t *as, int dest_r32, int src_r32); @@ -186,6 +188,8 @@ void asm_x86_call_ind(asm_x86_t *as, size_t fun_id, mp_uint_t n_args, int temp_r #define ASM_MOV_REG_LOCAL_ADDR(as, reg_dest, local_num) asm_x86_mov_local_addr_to_r32((as), (local_num), (reg_dest)) #define ASM_MOV_REG_PCREL(as, reg_dest, label) asm_x86_mov_reg_pcrel((as), (reg_dest), (label)) +#define ASM_NOT_REG(as, reg) asm_x86_not_r32((as), (reg)) +#define ASM_NEG_REG(as, reg) asm_x86_neg_r32((as), (reg)) #define ASM_LSL_REG(as, reg) asm_x86_shl_r32_cl((as), (reg)) #define ASM_LSR_REG(as, reg) asm_x86_shr_r32_cl((as), (reg)) #define ASM_ASR_REG(as, reg) asm_x86_sar_r32_cl((as), (reg)) diff --git a/py/asmxtensa.h b/py/asmxtensa.h index 5bd6426a14..aef9e74625 100644 --- a/py/asmxtensa.h +++ b/py/asmxtensa.h @@ -211,6 +211,10 @@ static inline void asm_xtensa_op_mull(asm_xtensa_t *as, uint reg_dest, uint reg_ asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRR(0, 2, 8, reg_dest, reg_src_a, reg_src_b)); } +static inline void asm_xtensa_op_neg(asm_xtensa_t *as, uint reg_dest, uint reg_src) { + asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRR(0, 0, 6, reg_dest, 0, reg_src)); +} + static inline void asm_xtensa_op_or(asm_xtensa_t *as, uint reg_dest, uint reg_src_a, uint reg_src_b) { asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRR(0, 0, 2, reg_dest, reg_src_a, reg_src_b)); } @@ -371,6 +375,7 @@ void asm_xtensa_call_ind_win(asm_xtensa_t *as, uint idx); #define ASM_MOV_REG_LOCAL_ADDR(as, reg_dest, local_num) asm_xtensa_mov_reg_local_addr((as), (reg_dest), ASM_NUM_REGS_SAVED + (local_num)) #define ASM_MOV_REG_PCREL(as, reg_dest, label) asm_xtensa_mov_reg_pcrel((as), (reg_dest), (label)) +#define ASM_NEG_REG(as, reg_dest) asm_xtensa_op_neg((as), (reg_dest), (reg_dest)) #define ASM_LSL_REG_REG(as, reg_dest, reg_shift) \ do { \ asm_xtensa_op_ssl((as), (reg_shift)); \ From b50efbd0e319764dab3f49b64519cfea5f2c2bab Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 18 Mar 2024 12:30:09 +1100 Subject: [PATCH 39/49] py/asmxtensa: Optimise asm_xtensa_mov_reg_i32_optimised() for tiny ints. Signed-off-by: Damien George --- py/asmxtensa.c | 4 +++- py/asmxtensa.h | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/py/asmxtensa.c b/py/asmxtensa.c index 84018402f6..0fbe351dcf 100644 --- a/py/asmxtensa.c +++ b/py/asmxtensa.c @@ -185,7 +185,9 @@ size_t asm_xtensa_mov_reg_i32(asm_xtensa_t *as, uint reg_dest, uint32_t i32) { } void asm_xtensa_mov_reg_i32_optimised(asm_xtensa_t *as, uint reg_dest, uint32_t i32) { - if (SIGNED_FIT12(i32)) { + if (-32 <= (int)i32 && (int)i32 <= 95) { + asm_xtensa_op_movi_n(as, reg_dest, i32); + } else if (SIGNED_FIT12(i32)) { asm_xtensa_op_movi(as, reg_dest, i32); } else { asm_xtensa_mov_reg_i32(as, reg_dest, i32); diff --git a/py/asmxtensa.h b/py/asmxtensa.h index aef9e74625..f226624a82 100644 --- a/py/asmxtensa.h +++ b/py/asmxtensa.h @@ -203,8 +203,9 @@ static inline void asm_xtensa_op_movi(asm_xtensa_t *as, uint reg_dest, int32_t i asm_xtensa_op24(as, ASM_XTENSA_ENCODE_RRI8(2, 10, (imm12 >> 8) & 0xf, reg_dest, imm12 & 0xff)); } -static inline void asm_xtensa_op_movi_n(asm_xtensa_t *as, uint reg_dest, int imm4) { - asm_xtensa_op16(as, ASM_XTENSA_ENCODE_RI7(12, reg_dest, imm4)); +// Argument must be in the range (-32 .. 95) inclusive. +static inline void asm_xtensa_op_movi_n(asm_xtensa_t *as, uint reg_dest, int imm7) { + asm_xtensa_op16(as, ASM_XTENSA_ENCODE_RI7(12, reg_dest, imm7)); } static inline void asm_xtensa_op_mull(asm_xtensa_t *as, uint reg_dest, uint reg_src_a, uint reg_src_b) { From 3c445f66369a49ba2dfa7e008d0a93c71fb1b6d3 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 18 Mar 2024 12:29:29 +1100 Subject: [PATCH 40/49] py/emitnative: Implement viper unary ops positive, negative and invert. Signed-off-by: Damien George --- py/emitnative.c | 33 +++++++++++++++++++++++----- tests/micropython/viper_error.py | 7 +++--- tests/micropython/viper_error.py.exp | 5 ++--- tests/micropython/viper_unop.py | 31 ++++++++++++++++++++++++++ tests/micropython/viper_unop.py.exp | 9 ++++++++ 5 files changed, 74 insertions(+), 11 deletions(-) create mode 100644 tests/micropython/viper_unop.py create mode 100644 tests/micropython/viper_unop.py.exp diff --git a/py/emitnative.c b/py/emitnative.c index f80461dd42..0b84a2ec8a 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -2259,15 +2259,38 @@ static void emit_native_pop_except_jump(emit_t *emit, mp_uint_t label, bool with } static void emit_native_unary_op(emit_t *emit, mp_unary_op_t op) { - vtype_kind_t vtype; - emit_pre_pop_reg(emit, &vtype, REG_ARG_2); - if (vtype == VTYPE_PYOBJ) { + vtype_kind_t vtype = peek_vtype(emit, 0); + if (vtype == VTYPE_INT || vtype == VTYPE_UINT) { + if (op == MP_UNARY_OP_POSITIVE) { + // No-operation, just leave the argument on the stack. + } else if (op == MP_UNARY_OP_NEGATIVE) { + int reg = REG_RET; + emit_pre_pop_reg_flexible(emit, &vtype, ®, reg, reg); + ASM_NEG_REG(emit->as, reg); + emit_post_push_reg(emit, vtype, reg); + } else if (op == MP_UNARY_OP_INVERT) { + #ifdef ASM_NOT_REG + int reg = REG_RET; + emit_pre_pop_reg_flexible(emit, &vtype, ®, reg, reg); + ASM_NOT_REG(emit->as, reg); + #else + int reg = REG_RET; + emit_pre_pop_reg_flexible(emit, &vtype, ®, REG_ARG_1, reg); + ASM_MOV_REG_IMM(emit->as, REG_ARG_1, -1); + ASM_XOR_REG_REG(emit->as, reg, REG_ARG_1); + #endif + emit_post_push_reg(emit, vtype, reg); + } else { + EMIT_NATIVE_VIPER_TYPE_ERROR(emit, + MP_ERROR_TEXT("'not' not implemented"), mp_binary_op_method_name[op]); + } + } else if (vtype == VTYPE_PYOBJ) { + emit_pre_pop_reg(emit, &vtype, REG_ARG_2); emit_call_with_imm_arg(emit, MP_F_UNARY_OP, op, REG_ARG_1); emit_post_push_reg(emit, VTYPE_PYOBJ, REG_RET); } else { - adjust_stack(emit, 1); EMIT_NATIVE_VIPER_TYPE_ERROR(emit, - MP_ERROR_TEXT("unary op %q not implemented"), mp_unary_op_method_name[op]); + MP_ERROR_TEXT("can't do unary op of '%q'"), vtype_to_qstr(vtype)); } } diff --git a/tests/micropython/viper_error.py b/tests/micropython/viper_error.py index 80617af0c1..6c5c3ba200 100644 --- a/tests/micropython/viper_error.py +++ b/tests/micropython/viper_error.py @@ -50,6 +50,9 @@ def f(): # incorrect return type test("@micropython.viper\ndef f() -> int: return []") +# can't do unary op of incompatible type +test("@micropython.viper\ndef f(x:ptr): -x") + # can't do binary op between incompatible types test("@micropython.viper\ndef f(): 1 + []") test("@micropython.viper\ndef f(x:int, y:uint): x < y") @@ -69,9 +72,7 @@ test("@micropython.viper\ndef f(x:ptr32): x[x] = None") test("@micropython.viper\ndef f(): raise 1") # unary ops not implemented -test("@micropython.viper\ndef f(x:int): +x") -test("@micropython.viper\ndef f(x:int): -x") -test("@micropython.viper\ndef f(x:int): ~x") +test("@micropython.viper\ndef f(x:int): not x") # binary op not implemented test("@micropython.viper\ndef f(x:uint, y:uint): res = x // y") diff --git a/tests/micropython/viper_error.py.exp b/tests/micropython/viper_error.py.exp index 31c85b1d87..51cbd6c709 100644 --- a/tests/micropython/viper_error.py.exp +++ b/tests/micropython/viper_error.py.exp @@ -5,6 +5,7 @@ ViperTypeError("local 'x' used before type known",) ViperTypeError("local 'x' has type 'int' but source is 'object'",) ViperTypeError("can't implicitly convert 'ptr' to 'bool'",) ViperTypeError("return expected 'int' but got 'object'",) +ViperTypeError("can't do unary op of 'ptr'",) ViperTypeError("can't do binary op between 'int' and 'object'",) ViperTypeError('comparison of int and uint',) ViperTypeError("can't load from 'int'",) @@ -15,9 +16,7 @@ ViperTypeError("can't store to 'int'",) ViperTypeError("can't store 'None'",) ViperTypeError("can't store 'None'",) ViperTypeError('must raise an object',) -ViperTypeError('unary op __pos__ not implemented',) -ViperTypeError('unary op __neg__ not implemented',) -ViperTypeError('unary op __invert__ not implemented',) +ViperTypeError("'not' not implemented",) ViperTypeError('div/mod not implemented for uint',) ViperTypeError('div/mod not implemented for uint',) ViperTypeError('binary op not implemented',) diff --git a/tests/micropython/viper_unop.py b/tests/micropython/viper_unop.py new file mode 100644 index 0000000000..61cbd5125f --- /dev/null +++ b/tests/micropython/viper_unop.py @@ -0,0 +1,31 @@ +# test unary operators + + +@micropython.viper +def pos(x: int) -> int: + return +x + + +print(pos(0)) +print(pos(1)) +print(pos(-2)) + + +@micropython.viper +def neg(x: int) -> int: + return -x + + +print(neg(0)) +print(neg(1)) +print(neg(-2)) + + +@micropython.viper +def inv(x: int) -> int: + return ~x + + +print(inv(0)) +print(inv(1)) +print(inv(-2)) diff --git a/tests/micropython/viper_unop.py.exp b/tests/micropython/viper_unop.py.exp new file mode 100644 index 0000000000..6d93312caa --- /dev/null +++ b/tests/micropython/viper_unop.py.exp @@ -0,0 +1,9 @@ +0 +1 +-2 +0 +-1 +2 +-1 +-2 +1 From 9651046edd4ebf6e2141f889c6068f4ed565f13f Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 6 Mar 2024 19:01:46 +1100 Subject: [PATCH 41/49] stm32/mboot: Add support for a raw filesystem. This is enabled by default if MBOOT_FSLOAD is enabled, although a board can explicitly disable it by `#define MBOOT_VFS_RAW (0)`. Signed-off-by: Damien George --- ports/stm32/mboot/Makefile | 1 + ports/stm32/mboot/README.md | 16 ++++-- ports/stm32/mboot/fsload.c | 59 +++++++++++++++++++---- ports/stm32/mboot/fwupdate.py | 50 +++++++++++++------ ports/stm32/mboot/mboot.h | 26 ++++++++++ ports/stm32/mboot/vfs.h | 18 +++++++ ports/stm32/mboot/vfs_raw.c | 91 +++++++++++++++++++++++++++++++++++ 7 files changed, 232 insertions(+), 29 deletions(-) create mode 100644 ports/stm32/mboot/vfs_raw.c diff --git a/ports/stm32/mboot/Makefile b/ports/stm32/mboot/Makefile index 389f9f5d0f..07053b3293 100755 --- a/ports/stm32/mboot/Makefile +++ b/ports/stm32/mboot/Makefile @@ -120,6 +120,7 @@ SRC_C += \ ui.c \ vfs_fat.c \ vfs_lfs.c \ + vfs_raw.c \ drivers/bus/softspi.c \ drivers/bus/softqspi.c \ drivers/memory/spiflash.c \ diff --git a/ports/stm32/mboot/README.md b/ports/stm32/mboot/README.md index 73e32fcedc..221e3a7c3a 100644 --- a/ports/stm32/mboot/README.md +++ b/ports/stm32/mboot/README.md @@ -71,13 +71,23 @@ How to use #define MBOOT_FSLOAD (1) and then enable one or more of the following depending on what filesystem - support is required in Mboot (note that the FAT driver is read-only and - quite compact, but littlefs supports both read and write so is rather - large): + support is required in Mboot: #define MBOOT_VFS_FAT (1) #define MBOOT_VFS_LFS1 (1) #define MBOOT_VFS_LFS2 (1) + #define MBOOT_VFS_RAW (1) + + Note that the FAT and LFS2 drivers are read-only and quite compact, but + LFS1 supports both read and write so is rather large. + + The raw filesystem type is enabled by default and is a flat section of + storage containing a single file without any metadata. The raw filesystem + can either be one regoin, or split over two separate, contiguous regions. + The latter is useful for wear levelling: given a chunk of flash, write the + firmware starting at a random block within that chunk and wrap around to + the beginning of the chunk when the end is reached. Then use a split + raw filesystem to inform mboot of this wrapping. 2. Build the board's main application firmware as usual. diff --git a/ports/stm32/mboot/fsload.c b/ports/stm32/mboot/fsload.c index abc3514ed1..83905eb491 100644 --- a/ports/stm32/mboot/fsload.c +++ b/ports/stm32/mboot/fsload.c @@ -39,7 +39,7 @@ #if MBOOT_FSLOAD -#if !(MBOOT_VFS_FAT || MBOOT_VFS_LFS1 || MBOOT_VFS_LFS2) +#if !(MBOOT_VFS_FAT || MBOOT_VFS_LFS1 || MBOOT_VFS_LFS2 || MBOOT_VFS_RAW) #error Must enable at least one VFS component #endif @@ -234,28 +234,51 @@ int fsload_process(void) { // End of elements. return -MBOOT_ERRNO_FSLOAD_NO_MOUNT; } + + // Extract element arguments based on the element length: + // - 10 bytes: mount_point fs_type uint32_t uint32_t + // - 14 bytes: mount_point fs_type uint32_t uint32_t uint32_t + // - 18 bytes: mount_point fs_type uint32_t uint32_t uint32_t uint32_t + // - 22 bytes: mount_point fs_type uint64_t uint64_t uint32_t + // - 34 bytes: mount_point fs_type uint64_t uint64_t uint64_t uint64_t mboot_addr_t base_addr; mboot_addr_t byte_len; - uint32_t block_size = MBOOT_FSLOAD_DEFAULT_BLOCK_SIZE; - if (elem[-1] == 10 || elem[-1] == 14) { + mboot_addr_t arg2 = 0; + mboot_addr_t arg3 = 0; + (void)arg2; + (void)arg3; + uint8_t elem_len = elem[-1]; + if (elem_len == 10 || elem_len == 14 || elem_len == 18) { // 32-bit base and length given, extract them. base_addr = get_le32(&elem[2]); byte_len = get_le32(&elem[6]); - if (elem[-1] == 14) { - // Block size given, extract it. - block_size = get_le32(&elem[10]); + if (elem_len >= 14) { + // Argument 2 given, extract it. + arg2 = get_le32(&elem[10]); + if (elem_len == 18) { + // Argument 3 given, extract it. + arg3 = get_le32(&elem[14]); + } } #if MBOOT_ADDRESS_SPACE_64BIT - } else if (elem[-1] == 22) { - // 64-bit base and length given, and block size, so extract them. + } else if (elem_len == 22 || elem_len == 34) { + // 64-bit base and length given, so extract them. base_addr = get_le64(&elem[2]); byte_len = get_le64(&elem[10]); - block_size = get_le32(&elem[18]); + if (elem_len == 22) { + // 32-bit argument 2 given, extract it. + arg2 = get_le32(&elem[18]); + } else { + // 64-bit argument 2 and 3 given, extract them. + arg2 = get_le64(&elem[18]); + arg3 = get_le64(&elem[26]); + } #endif } else { // Invalid MOUNT element. return -MBOOT_ERRNO_FSLOAD_INVALID_MOUNT; } + if (elem[0] == mount_point) { int ret; union { @@ -268,27 +291,43 @@ int fsload_process(void) { #if MBOOT_VFS_LFS2 vfs_lfs2_context_t lfs2; #endif + #if MBOOT_VFS_RAW + vfs_raw_context_t raw; + #endif } ctx; const stream_methods_t *methods; #if MBOOT_VFS_FAT if (elem[1] == ELEM_MOUNT_FAT) { - (void)block_size; ret = vfs_fat_mount(&ctx.fat, base_addr, byte_len); methods = &vfs_fat_stream_methods; } else #endif #if MBOOT_VFS_LFS1 if (elem[1] == ELEM_MOUNT_LFS1) { + uint32_t block_size = arg2; + if (block_size == 0) { + block_size = MBOOT_FSLOAD_DEFAULT_BLOCK_SIZE; + } ret = vfs_lfs1_mount(&ctx.lfs1, base_addr, byte_len, block_size); methods = &vfs_lfs1_stream_methods; } else #endif #if MBOOT_VFS_LFS2 if (elem[1] == ELEM_MOUNT_LFS2) { + uint32_t block_size = arg2; + if (block_size == 0) { + block_size = MBOOT_FSLOAD_DEFAULT_BLOCK_SIZE; + } ret = vfs_lfs2_mount(&ctx.lfs2, base_addr, byte_len, block_size); methods = &vfs_lfs2_stream_methods; } else #endif + #if MBOOT_VFS_RAW + if (elem[1] == ELEM_MOUNT_RAW) { + ret = vfs_raw_mount(&ctx.raw, base_addr, byte_len, arg2, arg3); + methods = &vfs_raw_stream_methods; + } else + #endif { // Unknown filesystem type return -MBOOT_ERRNO_FSLOAD_INVALID_MOUNT; diff --git a/ports/stm32/mboot/fwupdate.py b/ports/stm32/mboot/fwupdate.py index 47ceb19ba5..8578ff4fc9 100644 --- a/ports/stm32/mboot/fwupdate.py +++ b/ports/stm32/mboot/fwupdate.py @@ -9,6 +9,7 @@ import deflate, machine, stm VFS_FAT = 1 VFS_LFS1 = 2 VFS_LFS2 = 3 +VFS_RAW = 4 # Constants for creating mboot elements. _ELEM_TYPE_END = const(1) @@ -226,28 +227,45 @@ def _create_element(kind, body): def update_app_elements( - filename, fs_base, fs_len, fs_type=VFS_FAT, fs_blocksize=0, status_addr=None, addr_64bit=False + filename, + fs_base, + fs_len, + fs_type=VFS_FAT, + fs_blocksize=0, + status_addr=None, + addr_64bit=False, + *, + fs_base2=0, + fs_len2=0, ): - # Check firmware is of .dfu or .dfu.gz type - try: - with open(filename, "rb") as f: - hdr = deflate.DeflateIO(f, deflate.GZIP).read(6) - except Exception: - with open(filename, "rb") as f: - hdr = f.read(6) - if hdr != b"DfuSe\x01": - print("Firmware must be a .dfu(.gz) file.") - return () + if fs_type != VFS_RAW: + # Check firmware is of .dfu or .dfu.gz type + try: + with open(filename, "rb") as f: + hdr = deflate.DeflateIO(f, deflate.GZIP).read(6) + except Exception: + with open(filename, "rb") as f: + hdr = f.read(6) + if hdr != b"DfuSe\x01": + print("Firmware must be a .dfu(.gz) file.") + return () if fs_type in (VFS_LFS1, VFS_LFS2) and not fs_blocksize: raise Exception("littlefs requires fs_blocksize parameter") mount_point = 1 - mount_encoding = "seg0_base_addr = seg0_base_addr; + ctx->seg0_byte_len = seg0_byte_len; + ctx->seg1_base_addr = seg1_base_addr; + ctx->seg1_byte_len = seg1_byte_len; + return 0; +} + +static int vfs_raw_stream_open(void *stream_in, const char *fname) { + vfs_raw_context_t *stream = stream_in; + (void)fname; + stream->file_pos = 0; + return 0; +} + +static void vfs_raw_stream_close(void *stream_in) { + (void)stream_in; +} + +static int vfs_raw_stream_read(void *stream_in, uint8_t *buf, size_t len) { + vfs_raw_context_t *stream = stream_in; + size_t orig_len = len; + while (len) { + mboot_addr_t addr; + mboot_addr_t remain; + if (stream->file_pos < stream->seg0_byte_len) { + // Reading from segment 0. + mboot_addr_t seg0_pos = stream->file_pos; + addr = stream->seg0_base_addr + seg0_pos; + remain = stream->seg0_byte_len - seg0_pos; + } else { + // Reading from segment 1. + mboot_addr_t seg1_pos = stream->file_pos - stream->seg0_byte_len; + addr = stream->seg1_base_addr + seg1_pos; + remain = stream->seg1_byte_len - seg1_pos; + if (!remain) { + // At the end of segment 1. + break; + } + } + size_t l = MIN(len, remain); + hw_read(addr, l, buf); + stream->file_pos += l; + buf += l; + len -= l; + } + return orig_len - len; +} + +const stream_methods_t vfs_raw_stream_methods = { + vfs_raw_stream_open, + vfs_raw_stream_close, + vfs_raw_stream_read, +}; + +#endif // MBOOT_FSLOAD && MBOOT_VFS_RAW From 899592ac341ed9f10ee554424f964f6880fc8c48 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 13 Mar 2024 18:03:19 +1100 Subject: [PATCH 42/49] stm32/boards/LEGO_HUB_NO6: Move robust logic to mboot. Signed-off-by: Damien George --- ports/stm32/boards/LEGO_HUB_NO6/appupdate.py | 12 ------------ ports/stm32/boards/LEGO_HUB_NO6/board_init.c | 9 ++++++++- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/ports/stm32/boards/LEGO_HUB_NO6/appupdate.py b/ports/stm32/boards/LEGO_HUB_NO6/appupdate.py index 02398e8124..9cfbc06e56 100644 --- a/ports/stm32/boards/LEGO_HUB_NO6/appupdate.py +++ b/ports/stm32/boards/LEGO_HUB_NO6/appupdate.py @@ -7,9 +7,6 @@ import struct, machine, fwupdate, spiflash, pyb _IOCTL_BLOCK_COUNT = const(4) _IOCTL_BLOCK_SIZE = const(5) -_SPIFLASH_UPDATE_KEY_ADDR = const(1020 * 1024) -_SPIFLASH_UPDATE_KEY_VALUE = const(0x12345678) - _FILESYSTEM_ADDR = const(0x8000_0000 + 1024 * 1024) # Roundabout way to get actual filesystem size from config. @@ -26,9 +23,6 @@ def update_app(filename): if not elems: return - # Create the update key. - key = struct.pack(" Date: Thu, 14 Mar 2024 10:28:00 +1100 Subject: [PATCH 43/49] stm32/boards/LEGO_HUB_NO6: Use a raw filesystem for mboot to update fw. The C-based SPI flash driver is needed because the `_copy_file_to_raw_filesystem()` function must copy from a filesystem (eg FAT) to another part of flash, and the same C code must be used for both reading (from FAT) and writing (to flash). Signed-off-by: Damien George --- ports/stm32/boards/LEGO_HUB_NO6/appupdate.py | 68 +++++--- ports/stm32/boards/LEGO_HUB_NO6/manifest.py | 1 - ports/stm32/boards/LEGO_HUB_NO6/spiflash.c | 166 +++++++++++++++++++ ports/stm32/boards/LEGO_HUB_NO6/spiflash.py | 90 ---------- 4 files changed, 211 insertions(+), 114 deletions(-) create mode 100644 ports/stm32/boards/LEGO_HUB_NO6/spiflash.c delete mode 100644 ports/stm32/boards/LEGO_HUB_NO6/spiflash.py diff --git a/ports/stm32/boards/LEGO_HUB_NO6/appupdate.py b/ports/stm32/boards/LEGO_HUB_NO6/appupdate.py index 9cfbc06e56..8229004fbb 100644 --- a/ports/stm32/boards/LEGO_HUB_NO6/appupdate.py +++ b/ports/stm32/boards/LEGO_HUB_NO6/appupdate.py @@ -1,39 +1,61 @@ # Application firmware update function for LEGO_HUB_NO6. -# MIT license; Copyright (c) 2022 Damien P. George +# MIT license; Copyright (c) 2022-2024 Damien P. George from micropython import const -import struct, machine, fwupdate, spiflash, pyb +import random, struct, machine, fwupdate, spiflash, pyb _IOCTL_BLOCK_COUNT = const(4) _IOCTL_BLOCK_SIZE = const(5) -_FILESYSTEM_ADDR = const(0x8000_0000 + 1024 * 1024) +# Mboot addresses the external SPI flash at this location. +_MBOOT_SPIFLASH_BASE_ADDR = 0x80000000 -# Roundabout way to get actual filesystem size from config. -# This takes into account the 1M "reserved" section of the flash memory. -flash = pyb.Flash(start=0) -_FILESYSTEM_LEN = flash.ioctl(_IOCTL_BLOCK_COUNT, None) * flash.ioctl(_IOCTL_BLOCK_SIZE, None) +# The raw filesystem is in the first 1MiB of external SPI flash, +# but skip the first and last flash sectors. +_RAW_FILESYSTEM_ADDR = const(4 * 1024) +_RAW_FILESYSTEM_LEN = const(1016 * 1024) + + +def _copy_file_to_raw_filesystem(filename, flash, block): + block_count = flash.ioctl(_IOCTL_BLOCK_COUNT, 0) + block_size = flash.ioctl(_IOCTL_BLOCK_SIZE, 0) + buf = bytearray(block_size) + with open(filename, "rb") as file: + while True: + n = file.readinto(buf) + if not n: + break + flash.writeblocks(block, buf) + block += 1 + if block >= block_count: + print("|", end="") + block = 0 + print(".", end="") + print() def update_app(filename): print(f"Updating application firmware from {filename}") - # Create the elements for the mboot filesystem-load operation. - elems = fwupdate.update_app_elements(filename, _FILESYSTEM_ADDR, _FILESYSTEM_LEN) - if not elems: - return - - # Create a SPI flash object. - spi = machine.SoftSPI( - sck=machine.Pin.board.FLASH_SCK, - mosi=machine.Pin.board.FLASH_MOSI, - miso=machine.Pin.board.FLASH_MISO, - baudrate=50_000_000, - ) - cs = machine.Pin(machine.Pin.board.FLASH_NSS, machine.Pin.OUT, value=1) - # We can't use pyb.Flash() because we need to write to the "reserved" 1M area. - flash = spiflash.SPIFlash(spi, cs) + flash = spiflash.SPIFlash(start=_RAW_FILESYSTEM_ADDR, len=_RAW_FILESYSTEM_LEN) + + # Partition the raw filesystem into two segments for wear levelling. + block_count = flash.ioctl(_IOCTL_BLOCK_COUNT, 0) + block_size = flash.ioctl(_IOCTL_BLOCK_SIZE, 0) + block_start = random.randrange(0, block_count) + print(f"Raw filesystem block layout: 0 .. {block_start} .. {block_count}") + + # Copy the file to the special raw filesystem. + _copy_file_to_raw_filesystem(filename, flash, block_start) # Enter mboot with a request to do a filesystem-load update. - machine.bootloader(elems) + # Note: the filename doesn't mean anything here, but still needs to be non-empty. + fwupdate.update_mpy( + filename, + fs_type=fwupdate.VFS_RAW, + fs_base=_MBOOT_SPIFLASH_BASE_ADDR + _RAW_FILESYSTEM_ADDR + block_start * block_size, + fs_len=(block_count - block_start) * block_size, + fs_base2=_MBOOT_SPIFLASH_BASE_ADDR + _RAW_FILESYSTEM_ADDR, + fs_len2=block_start * block_size, + ) diff --git a/ports/stm32/boards/LEGO_HUB_NO6/manifest.py b/ports/stm32/boards/LEGO_HUB_NO6/manifest.py index 1be4246e37..afbfdc0a82 100644 --- a/ports/stm32/boards/LEGO_HUB_NO6/manifest.py +++ b/ports/stm32/boards/LEGO_HUB_NO6/manifest.py @@ -4,5 +4,4 @@ include("$(PORT_DIR)/boards/manifest.py") # Modules for application firmware update. module("fwupdate.py", base_path="$(PORT_DIR)/mboot", opt=3) -module("spiflash.py", opt=3) module("appupdate.py", opt=3) diff --git a/ports/stm32/boards/LEGO_HUB_NO6/spiflash.c b/ports/stm32/boards/LEGO_HUB_NO6/spiflash.c new file mode 100644 index 0000000000..37df7c9530 --- /dev/null +++ b/ports/stm32/boards/LEGO_HUB_NO6/spiflash.c @@ -0,0 +1,166 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2024 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. + */ + +#if !BUILDING_MBOOT + +#include "py/runtime.h" +#include "extmod/vfs.h" + +#include "storage.h" + +// Expose the entire external SPI flash as an object with the block protocol. + +#define FLASH_SIZE (MICROPY_HW_SPIFLASH_OFFSET_BYTES + MICROPY_HW_SPIFLASH_SIZE_BITS / 8) +#define BLOCK_SIZE (4096) + +typedef struct _spi_flash_obj_t { + mp_obj_base_t base; + uint32_t start; // in bytes + uint32_t len; // in bytes +} spi_flash_obj_t; + +static const mp_obj_type_t spi_flash_type; + +static void spi_flash_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + spi_flash_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "SPIFlash(start=%u, len=%u)", self->start, self->len); +} + +static mp_obj_t spi_flash_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_start, ARG_len }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_len, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + spi_flash_obj_t *self = mp_obj_malloc(spi_flash_obj_t, &spi_flash_type); + + mp_int_t start = args[ARG_start].u_int; + if (!(0 <= start && start < FLASH_SIZE && start % BLOCK_SIZE == 0)) { + mp_raise_ValueError(NULL); + } + + mp_int_t len = args[ARG_len].u_int; + if (len == -1) { + len = FLASH_SIZE - start; + } else if (!(0 < len && start + len <= FLASH_SIZE && len % BLOCK_SIZE == 0)) { + mp_raise_ValueError(NULL); + } + + self->start = start; + self->len = len; + + return MP_OBJ_FROM_PTR(self); +} + +static mp_obj_t spi_flash_readblocks(size_t n_args, const mp_obj_t *args) { + spi_flash_obj_t *self = MP_OBJ_TO_PTR(args[0]); + uint32_t block_num = self->start / BLOCK_SIZE + mp_obj_get_int(args[1]); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_WRITE); + int ret = spi_bdev_readblocks_raw(&spi_bdev, bufinfo.buf, block_num, 0, bufinfo.len); + return MP_OBJ_NEW_SMALL_INT(ret); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(spi_flash_readblocks_obj, 3, 3, spi_flash_readblocks); + +static mp_obj_t spi_flash_writeblocks(size_t n_args, const mp_obj_t *args) { + spi_flash_obj_t *self = MP_OBJ_TO_PTR(args[0]); + uint32_t block_num = self->start / BLOCK_SIZE + mp_obj_get_int(args[1]); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ); + int ret = spi_bdev_eraseblocks_raw(&spi_bdev, block_num, bufinfo.len); + if (ret == 0) { + ret = spi_bdev_writeblocks_raw(&spi_bdev, bufinfo.buf, block_num, 0, bufinfo.len); + } + return MP_OBJ_NEW_SMALL_INT(ret); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(spi_flash_writeblocks_obj, 3, 3, spi_flash_writeblocks); + +static mp_obj_t spi_flash_ioctl(mp_obj_t self_in, mp_obj_t cmd_in, mp_obj_t arg_in) { + spi_flash_obj_t *self = MP_OBJ_TO_PTR(self_in); + + mp_int_t cmd = mp_obj_get_int(cmd_in); + switch (cmd) { + case MP_BLOCKDEV_IOCTL_INIT: + storage_init(); + return MP_OBJ_NEW_SMALL_INT(0); + + case MP_BLOCKDEV_IOCTL_DEINIT: + return MP_OBJ_NEW_SMALL_INT(0); + + case MP_BLOCKDEV_IOCTL_SYNC: + return MP_OBJ_NEW_SMALL_INT(0); + + case MP_BLOCKDEV_IOCTL_BLOCK_COUNT: { + mp_int_t n = self->len / BLOCK_SIZE; + return MP_OBJ_NEW_SMALL_INT(n); + } + + case MP_BLOCKDEV_IOCTL_BLOCK_SIZE: + return MP_OBJ_NEW_SMALL_INT(BLOCK_SIZE); + + default: + return mp_const_none; + } +} +static MP_DEFINE_CONST_FUN_OBJ_3(spi_flash_ioctl_obj, spi_flash_ioctl); + +static const mp_rom_map_elem_t spi_flash_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&spi_flash_readblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&spi_flash_writeblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&spi_flash_ioctl_obj) }, +}; +static MP_DEFINE_CONST_DICT(spi_flash_locals_dict, spi_flash_locals_dict_table); + +static MP_DEFINE_CONST_OBJ_TYPE( + spi_flash_type, + MP_QSTR_SPIFlash, + MP_TYPE_FLAG_NONE, + make_new, spi_flash_make_new, + print, spi_flash_print, + locals_dict, &spi_flash_locals_dict + ); + +/******************************************************************************/ +// The `spiflash` module. + +static const mp_rom_map_elem_t spiflash_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_spiflash) }, + + { MP_ROM_QSTR(MP_QSTR_SPIFlash), MP_ROM_PTR(&spi_flash_type) }, +}; +static MP_DEFINE_CONST_DICT(spiflash_module_globals, spiflash_module_globals_table); + +const mp_obj_module_t spiflash_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&spiflash_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_spiflash, spiflash_module); + +#endif diff --git a/ports/stm32/boards/LEGO_HUB_NO6/spiflash.py b/ports/stm32/boards/LEGO_HUB_NO6/spiflash.py deleted file mode 100644 index e483ace950..0000000000 --- a/ports/stm32/boards/LEGO_HUB_NO6/spiflash.py +++ /dev/null @@ -1,90 +0,0 @@ -# MicroPython driver for SPI flash -# MIT license; Copyright (c) 2022 Damien P. George - -from micropython import const - -_PAGE_SIZE = const(256) # maximum bytes writable in one SPI transfer -_CMD_WRITE = const(0x02) -_CMD_READ = const(0x03) -_CMD_RDSR = const(0x05) -_CMD_WREN = const(0x06) -_CMD_WRITE_32 = const(0x12) -_CMD_READ_32 = const(0x13) -_CMD_SEC_ERASE = const(0x20) -_CMD_SEC_ERASE_32 = const(0x21) -_CMD_JEDEC_ID = const(0x9F) - - -class SPIFlash: - def __init__(self, spi, cs): - self.spi = spi - self.cs = cs - self.id = self._get_id() - # flash chip on Hub No. 6 uses 32-bit addressing - _32_bit = self.id == b"\xef\x40\x19" - self._READ = _CMD_READ_32 if _32_bit else _CMD_READ - self._WRITE = _CMD_WRITE_32 if _32_bit else _CMD_WRITE - self._ERASE = _CMD_SEC_ERASE_32 if _32_bit else _CMD_SEC_ERASE - - def _get_id(self): - self.cs(0) - self.spi.write(bytearray([_CMD_JEDEC_ID])) - buf = self.spi.read(3) - self.cs(1) - return buf - - def _wait_wel1(self): - # wait WEL=1 - self.cs(0) - self.spi.write(bytearray([_CMD_RDSR])) - buf = bytearray(1) - while True: - self.spi.readinto(buf) - if buf[0] & 2: - break - self.cs(1) - - def _wait_wip0(self): - # wait WIP=0 - self.cs(0) - self.spi.write(bytearray([_CMD_RDSR])) - buf = bytearray(1) - while True: - self.spi.readinto(buf) - if not (buf[0] & 1): - break - self.cs(1) - - def _flash_modify(self, cmd, addr, buf): - self.cs(0) - self.spi.write(bytearray([_CMD_WREN])) - self.cs(1) - self._wait_wel1() - self.cs(0) - self.spi.write(bytearray([cmd, addr >> 24, addr >> 16, addr >> 8, addr])) - if buf: - self.spi.write(buf) - self.cs(1) - self._wait_wip0() - - def erase_block(self, addr): - self._flash_modify(self._ERASE, addr, None) - - def readinto(self, addr, buf): - self.cs(0) - self.spi.write(bytearray([self._READ, addr >> 16, addr >> 8, addr])) - self.spi.readinto(buf) - self.cs(1) - - def write(self, addr, buf): - offset = addr & (_PAGE_SIZE - 1) - remain = len(buf) - buf = memoryview(buf) - buf_offset = 0 - while remain: - l = min(_PAGE_SIZE - offset, remain) - self._flash_modify(self._WRITE, addr, buf[buf_offset : buf_offset + l]) - remain -= l - addr += l - buf_offset += l - offset = 0 From 1c6012b0b5c62f18130217f30e73ad3ce4c8c9e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20D=C3=B6rre?= Date: Tue, 10 Oct 2023 16:13:24 +0000 Subject: [PATCH 44/49] extmod/modnetwork: Implement IPv6 API to set and get NIC configuration. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit implements a new .ipconfig() function for the NIC classes that use lwIP, to set and retrieve network configuration for a NIC. Currently this method supports: - ipconfig("addr4"): obtain a tuple (addr, netmask) of the currently configured ipv4 address - ipconfig("addr6"): obtain a list of tuples (addr, state, prefered_lifetime, valid_lifetime) of all currently active ipv6 addresses; this includes static, slaac and link-local addresses - ipconfig("has_dhcp4"): whether ipv4 dhcp has supplied an address - ipconfig("has_autoconf6"): if there is a valid, non-static ipv6 address - ipconfig(addr4="1.2.3.4/24"): to set the ipv4 address and netmask - ipconfig(addr6="2a01::2"): to set a static ipv6 address; note that this does not configure an interface route, as this does not seem supported by lwIP - ipconfig(autoconf6=True): to enable ipv6 network configuration with slaac - ipconfig(gw4="1.2.3.1"): to set the ipv4 gateway - ipconfig(dhcp4=True): enable ipv4 dhcp; this sets ipv4 address, netmask, gateway and a dns server - ipconfig(dhcp4=False): stops dhcp, releases the ip, and clears the configured ipv4 address. - ipconfig(dhcp6=True): enable stateless dhcpv6 to obtain a dns server There is also a new global configuration function network.ipconfig() that supports the following: - network.ipconfig(dns="2a01::2"): set the primary dns server (can be a ipv4 or ipv6 address) - network.ipconfig(prefer=6): to prefer ipv6 addresses to be returned as dns responses (falling back to ipv4 if the host does not have an ipv6 address); note that this does not flush the dns cache, so if a host is already in the dns cache with its v4 address, subsequent lookups will return that address even if prefer=6 is set This interface replaces NIC.ifconfig() completely, and ifconfig() should be marked as deprecated and removed in a future version. Signed-off-by: Felix Dörre --- extmod/modlwip.c | 11 +- extmod/modnetwork.c | 7 + extmod/modnetwork.h | 8 + extmod/network_cyw43.c | 7 + extmod/network_esp_hosted.c | 8 + extmod/network_lwip.c | 296 ++++++++++++++++++++++++++++++++++++ extmod/network_wiznet5k.c | 9 ++ 7 files changed, 345 insertions(+), 1 deletion(-) diff --git a/extmod/modlwip.c b/extmod/modlwip.c index 607143bb7e..d4e25917a6 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -38,6 +38,7 @@ #if MICROPY_PY_LWIP #include "shared/netutils/netutils.h" +#include "modnetwork.h" #include "lwip/init.h" #include "lwip/tcp.h" @@ -353,8 +354,12 @@ static void lwip_socket_free_incoming(lwip_socket_obj_t *socket) { } } +#if LWIP_VERSION_MAJOR < 2 +#define IPADDR_STRLEN_MAX 46 +#endif mp_obj_t lwip_format_inet_addr(const ip_addr_t *ip, mp_uint_t port) { - char *ipstr = ipaddr_ntoa(ip); + char ipstr[IPADDR_STRLEN_MAX]; + ipaddr_ntoa_r(ip, ipstr, sizeof(ipstr)); mp_obj_t tuple[2] = { tuple[0] = mp_obj_new_str(ipstr, strlen(ipstr)), tuple[1] = mp_obj_new_int(port), @@ -1750,7 +1755,11 @@ static mp_obj_t lwip_getaddrinfo(size_t n_args, const mp_obj_t *args) { state.status = 0; MICROPY_PY_LWIP_ENTER + #if LWIP_VERSION_MAJOR < 2 err_t ret = dns_gethostbyname(host, (ip_addr_t *)&state.ipaddr, lwip_getaddrinfo_cb, &state); + #else + err_t ret = dns_gethostbyname_addrtype(host, (ip_addr_t *)&state.ipaddr, lwip_getaddrinfo_cb, &state, mp_mod_network_prefer_dns_use_ip_version == 4 ? LWIP_DNS_ADDRTYPE_IPV4_IPV6 : LWIP_DNS_ADDRTYPE_IPV6_IPV4); + #endif MICROPY_PY_LWIP_EXIT switch (ret) { diff --git a/extmod/modnetwork.c b/extmod/modnetwork.c index c8d2b9e3ff..aa237bd93c 100644 --- a/extmod/modnetwork.c +++ b/extmod/modnetwork.c @@ -141,10 +141,17 @@ mp_obj_t mod_network_hostname(size_t n_args, const mp_obj_t *args) { } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_network_hostname_obj, 0, 1, mod_network_hostname); +#if LWIP_VERSION_MAJOR >= 2 +MP_DEFINE_CONST_FUN_OBJ_KW(mod_network_ipconfig_obj, 0, mod_network_ipconfig); +#endif + static const mp_rom_map_elem_t mp_module_network_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_network) }, { MP_ROM_QSTR(MP_QSTR_country), MP_ROM_PTR(&mod_network_country_obj) }, { MP_ROM_QSTR(MP_QSTR_hostname), MP_ROM_PTR(&mod_network_hostname_obj) }, + #if LWIP_VERSION_MAJOR >= 2 + { MP_ROM_QSTR(MP_QSTR_ipconfig), MP_ROM_PTR(&mod_network_ipconfig_obj) }, + #endif // Defined per port in mpconfigport.h #ifdef MICROPY_PORT_NETWORK_INTERFACES diff --git a/extmod/modnetwork.h b/extmod/modnetwork.h index 0a7897faaa..2ff9ce09de 100644 --- a/extmod/modnetwork.h +++ b/extmod/modnetwork.h @@ -69,10 +69,18 @@ extern char mod_network_hostname_data[MICROPY_PY_NETWORK_HOSTNAME_MAX_LEN + 1]; mp_obj_t mod_network_hostname(size_t n_args, const mp_obj_t *args); #if MICROPY_PY_LWIP + +#include "lwip/init.h" + struct netif; void mod_network_lwip_init(void); void mod_network_lwip_poll_wrapper(uint32_t ticks_ms); mp_obj_t mod_network_nic_ifconfig(struct netif *netif, size_t n_args, const mp_obj_t *args); +#if LWIP_VERSION_MAJOR >= 2 +mp_obj_t mod_network_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs); +mp_obj_t mod_network_nic_ipconfig(struct netif *netif, size_t n_args, const mp_obj_t *args, mp_map_t *kwargs); +extern int mp_mod_network_prefer_dns_use_ip_version; +#endif #elif defined(MICROPY_PORT_NETWORK_INTERFACES) struct _mod_network_socket_obj_t; diff --git a/extmod/network_cyw43.c b/extmod/network_cyw43.c index 89e68e2488..d3543c5260 100644 --- a/extmod/network_cyw43.c +++ b/extmod/network_cyw43.c @@ -321,6 +321,12 @@ static mp_obj_t network_cyw43_ifconfig(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_cyw43_ifconfig_obj, 1, 2, network_cyw43_ifconfig); +static mp_obj_t network_cyw43_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + network_cyw43_obj_t *self = MP_OBJ_TO_PTR(args[0]); + return mod_network_nic_ipconfig(&self->cyw->netif[self->itf], n_args - 1, args + 1, kwargs); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(network_cyw43_ipconfig_obj, 1, network_cyw43_ipconfig); + static mp_obj_t network_cyw43_status(size_t n_args, const mp_obj_t *args) { network_cyw43_obj_t *self = MP_OBJ_TO_PTR(args[0]); (void)self; @@ -532,6 +538,7 @@ static const mp_rom_map_elem_t network_cyw43_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_disconnect), MP_ROM_PTR(&network_cyw43_disconnect_obj) }, { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&network_cyw43_isconnected_obj) }, { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&network_cyw43_ifconfig_obj) }, + { MP_ROM_QSTR(MP_QSTR_ipconfig), MP_ROM_PTR(&network_cyw43_ipconfig_obj) }, { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&network_cyw43_status_obj) }, { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&network_cyw43_config_obj) }, diff --git a/extmod/network_esp_hosted.c b/extmod/network_esp_hosted.c index 14e8be8b6a..f86eaad37c 100644 --- a/extmod/network_esp_hosted.c +++ b/extmod/network_esp_hosted.c @@ -204,6 +204,13 @@ static mp_obj_t network_esp_hosted_ifconfig(size_t n_args, const mp_obj_t *args) } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_esp_hosted_ifconfig_obj, 1, 2, network_esp_hosted_ifconfig); +static mp_obj_t network_esp_hosted_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + esp_hosted_obj_t *self = MP_OBJ_TO_PTR(args[0]); + void *netif = esp_hosted_wifi_get_netif(self->itf); + return mod_network_nic_ipconfig(netif, n_args - 1, args + 1, kwargs); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(network_esp_hosted_ipconfig_obj, 1, network_esp_hosted_ipconfig); + static mp_obj_t network_esp_hosted_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { esp_hosted_obj_t *self = MP_OBJ_TO_PTR(args[0]); @@ -299,6 +306,7 @@ static const mp_rom_map_elem_t network_esp_hosted_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_disconnect), MP_ROM_PTR(&network_esp_hosted_disconnect_obj) }, { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&network_esp_hosted_isconnected_obj) }, { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&network_esp_hosted_ifconfig_obj) }, + { MP_ROM_QSTR(MP_QSTR_ipconfig), MP_ROM_PTR(&network_esp_hosted_ipconfig_obj) }, { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&network_esp_hosted_config_obj) }, { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&network_esp_hosted_status_obj) }, { MP_ROM_QSTR(MP_QSTR_OPEN), MP_ROM_INT(ESP_HOSTED_SEC_OPEN) }, diff --git a/extmod/network_lwip.c b/extmod/network_lwip.c index caa30f6fff..97cb43902d 100644 --- a/extmod/network_lwip.c +++ b/extmod/network_lwip.c @@ -26,6 +26,7 @@ #include "py/runtime.h" #include "py/mphal.h" +#include "py/parsenum.h" #if MICROPY_PY_NETWORK && MICROPY_PY_LWIP @@ -40,9 +41,19 @@ #include "lwip/timeouts.h" #include "lwip/dns.h" #include "lwip/dhcp.h" +#include "lwip/nd6.h" +#include "lwip/dhcp6.h" +#include "lwip/prot/dhcp.h" +#include "lwip/prot/dhcp6.h" +#include +#include + +int mp_mod_network_prefer_dns_use_ip_version = 4; // Implementations of network methods that can be used by any interface. +// This function provides the implementation of nic.ifconfig, is deprecated and will be removed. +// Use network.ipconfig and nic.ipconfig instead. mp_obj_t mod_network_nic_ifconfig(struct netif *netif, size_t n_args, const mp_obj_t *args) { if (n_args == 0) { // Get IP addresses @@ -90,6 +101,291 @@ mp_obj_t mod_network_nic_ifconfig(struct netif *netif, size_t n_args, const mp_o } } +// This function provides the common implementation of network.ipconfig +mp_obj_t mod_network_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + if (kwargs->used == 0) { + // Get config value + if (n_args != 1) { + mp_raise_TypeError(MP_ERROR_TEXT("must query one param")); + } + + switch (mp_obj_str_get_qstr(args[0])) { + case MP_QSTR_dns: { + char addr_str[IPADDR_STRLEN_MAX]; + ipaddr_ntoa_r(dns_getserver(0), addr_str, sizeof(addr_str)); + return mp_obj_new_str(addr_str, strlen(addr_str)); + } + case MP_QSTR_prefer: { + return MP_OBJ_NEW_SMALL_INT(mp_mod_network_prefer_dns_use_ip_version); + } + default: { + mp_raise_ValueError(MP_ERROR_TEXT("unexpected key")); + break; + } + } + } else { + // Set config value(s) + if (n_args != 0) { + mp_raise_TypeError(MP_ERROR_TEXT("can't specify pos and kw args")); + } + + for (size_t i = 0; i < kwargs->alloc; ++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_dns: { + ip_addr_t dns; + size_t addr_len; + const char *addr_str = mp_obj_str_get_data(e->value, &addr_len); + if (!ipaddr_aton(addr_str, &dns)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid arguments as dns server")); + } + dns_setserver(0, &dns); + break; + } + case MP_QSTR_prefer: { + int value = mp_obj_get_int(e->value); + if (value != 4 && value != 6) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid prefer argument")); + } + mp_mod_network_prefer_dns_use_ip_version = value; + break; + } + default: { + mp_raise_ValueError(MP_ERROR_TEXT("unexpected key")); + break; + } + } + } + } + } + return mp_const_none; +} + +mp_obj_t mod_network_nic_ipconfig(struct netif *netif, size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + + if (kwargs->used == 0) { + // Get config value + if (n_args != 1) { + mp_raise_TypeError(MP_ERROR_TEXT("must query one param")); + } + + switch (mp_obj_str_get_qstr(args[0])) { + case MP_QSTR_dhcp4: { + struct dhcp *dhcp = netif_dhcp_data(netif); + return mp_obj_new_bool(dhcp != NULL && dhcp->state != DHCP_STATE_OFF); + } + case MP_QSTR_has_dhcp4: { + return mp_obj_new_bool(dhcp_supplied_address(netif)); + } + #if LWIP_IPV6_DHCP6 + case MP_QSTR_dhcp6: { + struct dhcp6 *dhcp = netif_dhcp6_data(netif); + return mp_obj_new_bool(dhcp != NULL && dhcp->state != DHCP6_STATE_OFF); + } + #endif + #if LWIP_IPV6_AUTOCONFIG + case MP_QSTR_autoconf6: { + return netif->ip6_autoconfig_enabled ? mp_const_true : mp_const_false; + } + case MP_QSTR_has_autoconf6: { + int found = 0; + for (int i = 1; i < LWIP_IPV6_NUM_ADDRESSES; i++) { + if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) && + !netif_ip6_addr_isstatic(netif, i)) { + found = 1; + break; + } + } + if (found) { + break; + } + return mp_obj_new_bool(found); + } + #endif + case MP_QSTR_addr4: { + mp_obj_t tuple[2] = { + netutils_format_ipv4_addr((uint8_t *)&netif->ip_addr, NETUTILS_BIG), + netutils_format_ipv4_addr((uint8_t *)&netif->netmask, NETUTILS_BIG), + }; + return mp_obj_new_tuple(2, tuple); + } + #if LWIP_IPV6 + case MP_QSTR_addr6: { + mp_obj_t addrs[LWIP_IPV6_NUM_ADDRESSES]; + size_t n_addrs = 0; + for (int i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) { + if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i))) { + char addr_str[IPADDR_STRLEN_MAX]; + ipaddr_ntoa_r(netif_ip_addr6(netif, i), addr_str, sizeof(addr_str)); + mp_obj_t tuple[4] = { + mp_obj_new_str(addr_str, strlen(addr_str)), + MP_OBJ_NEW_SMALL_INT(netif_ip6_addr_state(netif, i)), + MP_OBJ_NEW_SMALL_INT(netif_ip6_addr_pref_life(netif, i)), // preferred + MP_OBJ_NEW_SMALL_INT(netif_ip6_addr_valid_life(netif, i)) + }; + addrs[n_addrs++] = mp_obj_new_tuple(4, tuple); + } + } + return mp_obj_new_list(n_addrs, addrs); + } + #endif + case MP_QSTR_gw4: { + return netutils_format_ipv4_addr((uint8_t *)&netif->gw, NETUTILS_BIG); + } + default: { + mp_raise_ValueError(MP_ERROR_TEXT("unexpected key")); + break; + } + } + return mp_const_none; + } else { + // Set config value(s) + if (n_args != 0) { + mp_raise_TypeError(MP_ERROR_TEXT("can't specify pos and kw args")); + } + + for (size_t i = 0; i < kwargs->alloc; ++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_dhcp4: { + if (mp_obj_is_true(e->value)) { + if (dhcp_supplied_address(netif)) { + dhcp_renew(netif); + } else { + dhcp_release_and_stop(netif); + dhcp_start(netif); + } + } else { + dhcp_release_and_stop(netif); + } + break; + } + #if LWIP_IPV6_DHCP6 + case MP_QSTR_dhcp6: { + dhcp6_disable(netif); + dhcp6_enable_stateless(netif); + break; + } + #endif + #if LWIP_IPV6_AUTOCONFIG + case MP_QSTR_autoconf6: { + netif_set_ip6_autoconfig_enabled(netif, mp_obj_is_true(e->value)); + if (mp_obj_is_true(e->value)) { + nd6_restart_netif(netif); + } else { + // Clear out any non-static addresses, skip link-local address in slot 0 + for (i = 1; i < LWIP_IPV6_NUM_ADDRESSES; i++) { + if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) && + !netif_ip6_addr_isstatic(netif, i)) { + netif_ip6_addr_set_state(netif, i, IP6_ADDR_INVALID); + } + } + } + break; + } + #endif + case MP_QSTR_addr4: + case MP_QSTR_addr6: { + ip_addr_t ip_addr; + ip_addr_t netmask; + int prefix_bits = 32; + if (e->value != mp_const_none && mp_obj_is_str(e->value)) { + size_t addr_len; + const char *input_str = mp_obj_str_get_data(e->value, &addr_len); + char plain_ip[IPADDR_STRLEN_MAX]; + char *split = strchr(input_str, '/'); + const char *addr_str = input_str; + if (split) { + int to_copy = sizeof(plain_ip) - 1; + if (split - addr_str < to_copy) { + to_copy = split - addr_str; + } + memcpy(plain_ip, addr_str, to_copy); + mp_obj_t prefix_obj = mp_parse_num_integer(split + 1, strlen(split + 1), 10, NULL); + prefix_bits = mp_obj_get_int(prefix_obj); + } + if (mp_obj_str_get_qstr(args[0]) == MP_QSTR_addr4) { + uint32_t mask = -(1u << (32 - prefix_bits)); + ip_addr_set_ip4_u32_val(netmask, ((mask & 0xFF) << 24) | ((mask & 0xFF00) << 8) | ((mask >> 8) & 0xFF00) | ((mask >> 24) & 0xFF)); + } + if (!ipaddr_aton(addr_str, &ip_addr)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid arguments")); + } + if ((mp_obj_str_get_qstr(args[0]) == MP_QSTR_addr6) != IP_IS_V6(&ip_addr) + || (mp_obj_str_get_qstr(args[0]) == MP_QSTR_addr4) != IP_IS_V4(&ip_addr)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid address type")); + } + } else if (e->value != mp_const_none) { + mp_obj_t *items; + mp_obj_get_array_fixed_n(e->value, 2, &items); + size_t addr_len; + const char *ip_addr_str = mp_obj_str_get_data(items[0], &addr_len); + const char *netmask_str = mp_obj_str_get_data(items[1], &addr_len); + if (!ipaddr_aton(ip_addr_str, &ip_addr)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid arguments")); + } + if (!ipaddr_aton(netmask_str, &netmask)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid arguments")); + } + if (!IP_IS_V4(&ip_addr) || !IP_IS_V4(&netmask)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid address type")); + } + } + if (mp_obj_str_get_qstr(args[0]) == MP_QSTR_addr4) { + if (e->value != mp_const_none) { + netif->ip_addr = ip_addr; + netif->netmask = netmask; + } else { + ip4_addr_set_any(ip_2_ip4(&netif->ip_addr)); + ip4_addr_set_any(ip_2_ip4(&netif->netmask)); + } + #if LWIP_IPV6 + } else if (mp_obj_str_get_qstr(args[0]) == MP_QSTR_addr6) { + // Clear out any existing static addresses. Address 0 comes from autoconf. + for (i = 1; i < LWIP_IPV6_NUM_ADDRESSES; i++) { + if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) && + netif_ip6_addr_isstatic(netif, i)) { + netif_ip6_addr_set_state(netif, i, IP6_ADDR_INVALID); + } + } + if (e->value != mp_const_none) { + s8_t free_idx; + netif_add_ip6_address(netif, ip_2_ip6(&ip_addr), &free_idx); + netif_ip6_addr_set_valid_life(netif, free_idx, IP6_ADDR_LIFE_STATIC); + netif_ip6_addr_set_pref_life(netif, free_idx, IP6_ADDR_LIFE_STATIC); + netif_ip6_addr_set_state(netif, free_idx, IP6_ADDR_PREFERRED); + } + #endif + } + break; + } + case MP_QSTR_gw4: { + ip_addr_t ip_addr; + size_t addr_len; + const char *addr_str = mp_obj_str_get_data(e->value, &addr_len); + if (!ipaddr_aton(addr_str, &ip_addr)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid arguments")); + } + if (IP_IS_V4(&ip_addr)) { + netif->gw = ip_addr; + } else { + mp_raise_ValueError(MP_ERROR_TEXT("invalid address type")); + } + break; + } + default: { + mp_raise_ValueError(MP_ERROR_TEXT("unexpected key")); + break; + } + } + } + } + } + return mp_const_none; +} + #endif // LWIP_VERSION_MAJOR >= 2 #endif // MICROPY_PY_NETWORK && MICROPY_PY_LWIP diff --git a/extmod/network_wiznet5k.c b/extmod/network_wiznet5k.c index d1aadc3e15..b550f8c1f4 100644 --- a/extmod/network_wiznet5k.c +++ b/extmod/network_wiznet5k.c @@ -915,6 +915,12 @@ static mp_obj_t wiznet5k_ifconfig(size_t n_args, const mp_obj_t *args) { } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(wiznet5k_ifconfig_obj, 1, 2, wiznet5k_ifconfig); +static mp_obj_t network_wiznet5k_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + wiznet5k_obj_t *self = MP_OBJ_TO_PTR(args[0]); + return mod_network_nic_ipconfig(&self->netif, n_args - 1, args + 1, kwargs); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(wiznet5k_ipconfig_obj, 1, network_wiznet5k_ipconfig); + static mp_obj_t send_ethernet_wrapper(mp_obj_t self_in, mp_obj_t buf_in) { wiznet5k_obj_t *self = MP_OBJ_TO_PTR(self_in); mp_buffer_info_t buf; @@ -1008,6 +1014,9 @@ static const mp_rom_map_elem_t wiznet5k_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&wiznet5k_isconnected_obj) }, { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&wiznet5k_active_obj) }, { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&wiznet5k_ifconfig_obj) }, + #if WIZNET5K_WITH_LWIP_STACK + { MP_ROM_QSTR(MP_QSTR_ipconfig), MP_ROM_PTR(&wiznet5k_ipconfig_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&wiznet5k_status_obj) }, { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&wiznet5k_config_obj) }, #if WIZNET5K_WITH_LWIP_STACK From 77f08b72caeb26c7e3be5975d976e77d44099d43 Mon Sep 17 00:00:00 2001 From: Nicko van Someren Date: Thu, 28 Dec 2023 14:22:40 -0700 Subject: [PATCH 45/49] docs/library/rp2.DMA: Add documentation for rp2 DMA support. Signed-off-by: Nicko van Someren --- docs/library/rp2.DMA.rst | 293 ++++++++++++++++++++++++++++++ docs/library/rp2.StateMachine.rst | 7 + docs/library/rp2.rst | 1 + 3 files changed, 301 insertions(+) create mode 100644 docs/library/rp2.DMA.rst diff --git a/docs/library/rp2.DMA.rst b/docs/library/rp2.DMA.rst new file mode 100644 index 0000000000..c5e3f31aa2 --- /dev/null +++ b/docs/library/rp2.DMA.rst @@ -0,0 +1,293 @@ +.. currentmodule:: rp2 +.. _rp2.DMA: + +class DMA -- access to the RP2040's DMA controller +================================================== + +The :class:`DMA` class offers access to the RP2040's Direct Memory Access (DMA) +controller, providing the ability move data between memory blocks and/or IO registers. The DMA +controller has its own, separate read and write bus master connections onto the bus fabric and +each DMA channel can independently read data from one address and write it back to another +address, optionally incrementing one or both pointers, allowing it to perform transfers on behalf +of the processor while the processor carries out other tasks or enters a low power state. The +RP2040's DMA controller has 12 independent DMA channels that can run concurrently. For full +details of the RP2040's DMA system see section 2.5 of the `RP2040 Datasheet +`_. + +Examples +-------- + +The simplest use of the DMA controller is to move data from one block of memory to another. +This can be accomplished with the following code:: + + a = bytearray(32*1024) + b = bytearray(32*1024) + d = rp2.DMA() + c = d.pack_ctrl() # Just use the default control value. + # The count is in 'transfers', which defaults to four-byte words, so divide length by 4 + d.config(read=a, write=b, count=len(a)//4, ctrl=c, trigger=True) + # Wait for completion + while d.active(): + pass + +Note that while this example sits in an idle loop while it waits for the transfer to complete, +the program could just as well do some useful work in this time instead. + +Another, perhaps more common use of the DMA controller is to transfer between memory and an IO +peripheral. In this situation the address of the IO register does not change for each transfer but +the memory address needs to be incremented. It is also necessary to control the pace of the +transfer so as to not write data before it can be accepted by a peripheral or read it before the +data is ready, and this can be controlled with the ``treq_sel`` field of the DMA channel's control +register. The various fields of the control register for each DMA channel can be packed +using the :meth:`DMA.pack_ctrl()` method and unpacked using the :meth:`DMA.unpack_ctrl()` +static method. Code to transfer data from a byte array to the TX FIFO of a PIO state machine, +one byte at a time, looks like this:: + + # pio_num is index of the PIO block being used, sm_num is the state machine in that block. + # my_state_machine is an rp2.PIO() instance. + DATA_REQUEST_INDEX = (pio_num << 3) + sm_num + + src_data = bytearray(1024) + d = rp2.DMA() + + # Transfer bytes, rather than words, don't increment the write address and pace the transfer. + c = d.pack_ctrl(size=0, inc_write=False, treq_sel=DATA_REQUEST_INDEX) + + d.config( + read=src_data, + write=my_state_machine, + count=len(src_data), + ctrl=c, + trigger=True + ) + +Note that in this example the value given for the write address is just the PIO state machine to +which we are sending the data. This works because PIO state machines present the buffer protocol, +allowing direct access to their data FIFO registers. + +Constructor +----------- + +.. class:: DMA() + + Claim one of the DMA controller channels for exclusive use. + +Methods +------- + +.. method:: DMA.config(read=None, write=None, count=None, ctrl=None, trigger=False) + + Configure the DMA registers for the channel and optionally start the transfer. + Parameters are: + + - *read*: The address from which the DMA controller will start reading data or + an object that will provide data to be read. It can be an integer or any + object that supports the buffer protocol. + - *write*: The address to which the DMA controller will start writing or an + object into which data will be written. It can be an integer or any object + that supports the buffer protocol. + - *count*: The number of bus transfers that will execute before this channel + stops. Note that this is the number of transfers, not the number of bytes. + If the transfers are 2 or 4 bytes wide then the total amount of data moved + (and thus the size of required buffer) needs to be multiplied accordingly. + - *ctrl*: The value for the DMA control register. This is an integer value + that is typically packed using the :meth:`DMA.pack_ctrl()`. + - *trigger*: Optionally commence the transfer immediately. + +.. method:: DMA.irq(handler=None, hard=False) + + Returns the IRQ object for this DMA channel and optionally configures it. + +.. method:: DMA.close() + + Release the claim on the underlying DMA channel and free the interrupt + handler. The :class:`DMA` object can not be used after this operation. + +.. method:: DMA.pack_ctrl(default=None, **kwargs) + + Pack the values provided in the keyword arguments into the named fields of a new control + register value. Any field that is not provided will be set to a default value. The + default will either be taken from the provided ``default`` value, or if that is not + given, a default suitable for the current channel; setting this to the current value + of the `DMA.ctrl` attribute provides an easy way to override a subset of the fields. + + The keys for the keyword arguments can be any key returned by the :meth:`DMA.unpack_ctrl()` + method. The writable values are: + + - *enable*: ``bool`` Set to enable the channel (default: ``True``). + + - *high_pri*: ``bool`` Make this channel's bus traffic high priority (default: ``False``). + + - *size*: ``int`` Transfer size: 0=byte, 1=half word, 2=word (default: 2). + + - *inc_read*: ``bool`` Increment the read address after each transfer (default: ``True``). + + - *inc_write*: ``bool`` Increment the write address after each transfer (default: ``True``). + + - *ring_size*: ``int`` If non-zero, only the bottom ``ring_size`` bits of one + address register will change when an address is incremented, causing the + address to wrap at the next ``1 << ring_size`` byte boundary. Which + address is wrapped is controlled by the ``ring_sel`` flag. A zero value + disables address wrapping. + + - *ring_sel*: ``bool`` Set to ``False`` to have the ``ring_size`` apply to the read address + or ``True`` to apply to the write address. + + - *chain_to*: ``int`` The channel number for a channel to trigger after this transfer + completes. Setting this value to this DMA object's own channel number + disables chaining (this is the default). + + - *treq_sel*: ``int`` Select a Transfer Request signal. See section 2.5.3 in the RP2040 + datasheet for details. + + - *irq_quiet*: ``bool`` Do not generate interrupt at the end of each transfer. Interrupts + will instead be generated when a zero value is written to the trigger + register, which will halt a sequence of chained transfers (default: + ``True``). + + - *bswap*: ``bool`` If set to true, bytes in words or half-words will be reversed before + writing (default: ``True``). + + - *sniff_en*: ``bool`` Set to ``True`` to allow data to be accessed by the chips sniff + hardware (default: ``False``). + + - *write_err*: ``bool`` Setting this to ``True`` will clear a previously reported write + error. + + - *read_err*: ``bool`` Setting this to ``True`` will clear a previously reported read + error. + + See the description of the ``CH0_CTRL_TRIG`` register in section 2.5.7 of the RP2040 + datasheet for details of all of these fields. + +.. method:: DMA.unpack_ctrl(value) + + Unpack a value for a DMA channel control register into a dictionary with key/value pairs + for each of the fields in the control register. *value* is the ``ctrl`` register value + to unpack. + + This method will return values for all the keys that can be passed to ``DMA.pack_ctrl``. + In addition, it will also return the read-only flags in the control register: ``busy``, + which goes high when a transfer starts and low when it ends, and ``ahb_err``, which is + the logical OR of the ``read_err`` and ``write_err`` flags. These values will be ignored + when packing, so that the dictionary created by unpacking a control register can be used + directly as the keyword arguments for packing. + +.. method:: DMA.active([value]) + + Gets or sets whether the DMA channel is currently running. + + >>> sm.active() + 0 + >>> sm.active(1) + >>> while sm.active(): + ... pass + +Attributes +---------- + +.. attribute:: DMA.read + + This attribute reflects the address from which the next bus transfer + will read. It may be written with either an integer or an object + that supports the buffer protocol and doing so has immediate effect. + +.. attribute:: DMA.write + + This attribute reflects the address to which the next bus transfer + will write. It may be written with either an integer or an object + that supports the buffer protocol and doing so has immediate effect. + +.. attribute:: DMA.count + + Reading this attribute will return the number of remaining bus + transfers in the *current* transfer sequence. Writing this attribute + sets the total number of transfers to be the *next* transfer sequence. + +.. attribute:: DMA.ctrl + + This attribute reflects DMA channel control register. It is typically written + with an integer packed using the :meth:`DMA.pack_ctrl()` method. The returned + register value can be unpacked using the :meth:`DMA.unpack_ctrl()` method. + +.. attribute:: DMA.channel + + The channel number of the DMA channel. This can be passed in the ``chain_to`` + argument of `DMA.pack_ctrl()` on another channel to allow DMA chaining. + +.. attribute:: DMA.registers + + This attribute is an array-like object that allows direct access to + the DMA channel's registers. The index is by word, rather than by byte, + so the register indices are the register address offsets divided by 4. + See the RP2040 data sheet for register details. + +Chaining and trigger register access +------------------------------------ + +The DMA controller in the RP2040 offers a couple advanced features to allow one DMA channel +to initiate a transfer on another channel. One is the use of the ``chain_to`` value in the +control register and the other is writing to one of the DMA channel's registers that has a +trigger effect. When coupled with the ability to have one DMA channel write directly to the +`DMA.registers` of another channel, this allows for complex transactions to be performed +without any CPU intervention. + +Below is an example of using both chaining and register +triggering to implement gathering of multiple blocks of data into a single destination. Full +details of these features can be found in section 2.5 of the RP2040 data sheet and the code +below is a Pythonic version of the example in sub-section 2.5.6.2. + +.. code-block:: python + + from rp2 import DMA + from uctypes import addressof + from array import array + + def gather_strings(string_list, buf): + # We use two DMA channels. The first sends lengths and source addresses from the gather + # list to the registers of the second. The second copies the data itself. + gather_dma = DMA() + buffer_dma = DMA() + + # Pack up length/address pairs to be sent to the registers. + gather_list = array("I") + + for s in string_list: + gather_list.append(len(s)) + gather_list.append(addressof(s)) + + gather_list.append(0) + gather_list.append(0) + + # When writing to the registers of the second DMA channel, we need to wrap the + # write address on an 8-byte (1<<3 bytes) boundary. We write to the ``TRANS_COUNT`` + # and ``READ_ADD_TRIG`` registers in the last register alias (registers 14 and 15). + gather_ctrl = gather_dma.pack_ctrl(ring_size=3, ring_sel=True) + gather_dma.config( + read=gather_list, write=buffer_dma.registers[14:16], + count=2, ctrl=gather_ctrl + ) + + # When copying the data, the transfer size is single bytes, and when completed we need + # to chain back to the start another gather DMA transaction. + buffer_ctrl = buffer_dma.pack_ctrl(size=0, chain_to=gather_dma.channel) + # The read and count values will be set by the other DMA channel. + buffer_dma.config(write=buf, ctrl=buffer_ctrl) + + # Set the transfer in motion. + gather_dma.active(1) + + # Wait until all the register values have been sent + end_address = addressof(gather_list) + 4 * len(gather_list) + while gather_dma.read != end_address: + pass + + input = ["This is ", "a ", "test", " of the scatter", " gather", " process"] + output = bytearray(64) + + print(output) + gather_strings(input, output) + print(output) + +This example idles while waiting for the transfer to complete; alternatively it could +set an interrupt handler and return immediately. diff --git a/docs/library/rp2.StateMachine.rst b/docs/library/rp2.StateMachine.rst index e8c167c09f..1cb87e90b6 100644 --- a/docs/library/rp2.StateMachine.rst +++ b/docs/library/rp2.StateMachine.rst @@ -140,3 +140,10 @@ Methods Optionally configure it. +Buffer protocol +--------------- + +The StateMachine class supports the `buffer protocol`, allowing direct access to the transmit +and receive FIFOs for each state machine. This is primarily in order to allow StateMachine +objects to be passed directly as the read or write parameters when configuring a `rp2.DMA()` +channel. diff --git a/docs/library/rp2.rst b/docs/library/rp2.rst index 7a473387b4..f0189327df 100644 --- a/docs/library/rp2.rst +++ b/docs/library/rp2.rst @@ -241,6 +241,7 @@ Classes .. toctree:: :maxdepth: 1 + rp2.DMA.rst rp2.Flash.rst rp2.PIO.rst rp2.StateMachine.rst From e520fa2e0fb3cfafe27a1f9e7e9b230dd58d7a33 Mon Sep 17 00:00:00 2001 From: Matthias Urlichs Date: Wed, 25 Oct 2023 19:17:47 +0200 Subject: [PATCH 46/49] py/binary: Support half-float 'e' format in struct pack/unpack. This commit implements the 'e' half-float format: 10-bit mantissa, 5-bit exponent. It uses native _Float16 if supported by the compiler, otherwise uses custom bitshifting encoding/decoding routines. Signed-off-by: Matthias Urlichs Signed-off-by: Damien George --- docs/library/struct.rst | 2 + py/binary.c | 109 +++++++++++++++++++++++++++++++++++- py/mpconfig.h | 9 +++ tests/float/float_struct.py | 2 +- 4 files changed, 119 insertions(+), 3 deletions(-) diff --git a/docs/library/struct.rst b/docs/library/struct.rst index 026cb5e8ac..c235750596 100644 --- a/docs/library/struct.rst +++ b/docs/library/struct.rst @@ -45,6 +45,8 @@ The following data types are supported: +--------+--------------------+-------------------+---------------+ | Q | unsigned long long | integer (`1`) | 8 | +--------+--------------------+-------------------+---------------+ +| e | n/a (half-float) | float (`2`) | 2 | ++--------+--------------------+-------------------+---------------+ | f | float | float (`2`) | 4 | +--------+--------------------+-------------------+---------------+ | d | double | float (`2`) | 8 | diff --git a/py/binary.c b/py/binary.c index 4c8b6ffcdc..7c01cfa1c8 100644 --- a/py/binary.c +++ b/py/binary.c @@ -74,11 +74,14 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) { case 'S': size = sizeof(void *); break; + case 'e': + size = 2; + break; case 'f': - size = sizeof(float); + size = 4; break; case 'd': - size = sizeof(double); + size = 8; break; } break; @@ -122,6 +125,10 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) { align = alignof(void *); size = sizeof(void *); break; + case 'e': + align = 2; + size = 2; + break; case 'f': align = alignof(float); size = sizeof(float); @@ -144,6 +151,99 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) { return size; } +#if MICROPY_PY_BUILTINS_FLOAT && MICROPY_FLOAT_USE_NATIVE_FLT16 + +static inline float mp_decode_half_float(uint16_t hf) { + union { + uint16_t i; + _Float16 f; + } fpu = { .i = hf }; + return fpu.f; +} + +static inline uint16_t mp_encode_half_float(float x) { + union { + uint16_t i; + _Float16 f; + } fp_sp = { .f = (_Float16)x }; + return fp_sp.i; +} + +#elif MICROPY_PY_BUILTINS_FLOAT + +static float mp_decode_half_float(uint16_t hf) { + union { + uint32_t i; + float f; + } fpu; + + uint16_t m = hf & 0x3ff; + int e = (hf >> 10) & 0x1f; + if (e == 0x1f) { + // Half-float is infinity. + e = 0xff; + } else if (e) { + // Half-float is normal. + e += 127 - 15; + } else if (m) { + // Half-float is subnormal, make it normal. + e = 127 - 15; + while (!(m & 0x400)) { + m <<= 1; + --e; + } + m -= 0x400; + ++e; + } + + fpu.i = ((hf & 0x8000) << 16) | (e << 23) | (m << 13); + return fpu.f; +} + +static uint16_t mp_encode_half_float(float x) { + union { + uint32_t i; + float f; + } fpu = { .f = x }; + + uint16_t m = (fpu.i >> 13) & 0x3ff; + if (fpu.i & (1 << 12)) { + // Round up. + ++m; + } + int e = (fpu.i >> 23) & 0xff; + + if (e == 0xff) { + // Infinity. + e = 0x1f; + } else if (e != 0) { + e -= 127 - 15; + if (e < 0) { + // Underflow: denormalized, or zero. + if (e >= -11) { + m = (m | 0x400) >> -e; + if (m & 1) { + m = (m >> 1) + 1; + } else { + m >>= 1; + } + } else { + m = 0; + } + e = 0; + } else if (e > 0x3f) { + // Overflow: infinity. + e = 0x1f; + m = 0; + } + } + + uint16_t bits = ((fpu.i >> 16) & 0x8000) | (e << 10) | m; + return bits; +} + +#endif + mp_obj_t mp_binary_get_val_array(char typecode, void *p, size_t index) { mp_int_t val = 0; switch (typecode) { @@ -240,6 +340,8 @@ mp_obj_t mp_binary_get_val(char struct_type, char val_type, byte *p_base, byte * const char *s_val = (const char *)(uintptr_t)(mp_uint_t)val; return mp_obj_new_str(s_val, strlen(s_val)); #if MICROPY_PY_BUILTINS_FLOAT + } else if (val_type == 'e') { + return mp_obj_new_float_from_f(mp_decode_half_float(val)); } else if (val_type == 'f') { union { uint32_t i; @@ -309,6 +411,9 @@ void mp_binary_set_val(char struct_type, char val_type, mp_obj_t val_in, byte *p val = (mp_uint_t)val_in; break; #if MICROPY_PY_BUILTINS_FLOAT + case 'e': + val = mp_encode_half_float(mp_obj_get_float_to_f(val_in)); + break; case 'f': { union { uint32_t i; diff --git a/py/mpconfig.h b/py/mpconfig.h index cb0f050a74..d9cff930d1 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -830,6 +830,15 @@ typedef double mp_float_t; #define MICROPY_PY_BUILTINS_COMPLEX (MICROPY_PY_BUILTINS_FLOAT) #endif +// Whether to use the native _Float16 for 16-bit float support +#ifndef MICROPY_FLOAT_USE_NATIVE_FLT16 +#ifdef __FLT16_MAX__ +#define MICROPY_FLOAT_USE_NATIVE_FLT16 (1) +#else +#define MICROPY_FLOAT_USE_NATIVE_FLT16 (0) +#endif +#endif + // Whether to provide a high-quality hash for float and complex numbers. // Otherwise the default is a very simple but correct hashing function. #ifndef MICROPY_FLOAT_HIGH_QUALITY_HASH diff --git a/tests/float/float_struct.py b/tests/float/float_struct.py index 47fe405018..6e282a6afe 100644 --- a/tests/float/float_struct.py +++ b/tests/float/float_struct.py @@ -8,7 +8,7 @@ except ImportError: i = 1.0 + 1 / 2 # TODO: it looks like '=' format modifier is not yet supported # for fmt in ('f', 'd', '>f', '>d', 'f", ">d", "e", ">f", ">d", " Date: Tue, 19 Mar 2024 22:56:38 +1100 Subject: [PATCH 47/49] stm32/stm32.mk: Enable _Float16 support on MCUs with hardware floats. Signed-off-by: Damien George --- ports/stm32/stm32.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/stm32.mk b/ports/stm32/stm32.mk index b9f70db73e..718fa8cf06 100644 --- a/ports/stm32/stm32.mk +++ b/ports/stm32/stm32.mk @@ -44,14 +44,14 @@ ifneq ($(BUILDING_MBOOT),1) SUPPORTS_HARDWARE_FP_SINGLE = 0 SUPPORTS_HARDWARE_FP_DOUBLE = 0 ifeq ($(CMSIS_MCU),$(filter $(CMSIS_MCU),STM32F765xx STM32F767xx STM32F769xx STM32H743xx STM32H747xx STM32H750xx STM32H7A3xx STM32H7A3xxQ STM32H7B3xx STM32H7B3xxQ)) -CFLAGS_CORTEX_M += -mfpu=fpv5-d16 -mfloat-abi=hard +CFLAGS_CORTEX_M += -mfpu=fpv5-d16 -mfloat-abi=hard -mfp16-format=ieee SUPPORTS_HARDWARE_FP_SINGLE = 1 SUPPORTS_HARDWARE_FP_DOUBLE = 1 else ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f0 g0 l0 l1 wl)) CFLAGS_CORTEX_M += -msoft-float else -CFLAGS_CORTEX_M += -mfpu=fpv4-sp-d16 -mfloat-abi=hard +CFLAGS_CORTEX_M += -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mfp16-format=ieee SUPPORTS_HARDWARE_FP_SINGLE = 1 endif endif From b80607ecafeb47664b35e4f255ed43171719ecf5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 19 Mar 2024 23:09:11 +1100 Subject: [PATCH 48/49] unix/variants: Don't use native _Float16 type. Using it increases code size by about 2k. Signed-off-by: Damien George --- ports/unix/variants/mpconfigvariant_common.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/unix/variants/mpconfigvariant_common.h b/ports/unix/variants/mpconfigvariant_common.h index 981babca9f..2e34055bf7 100644 --- a/ports/unix/variants/mpconfigvariant_common.h +++ b/ports/unix/variants/mpconfigvariant_common.h @@ -41,6 +41,11 @@ #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_DOUBLE) #endif +// Don't use native _Float16 because it increases code size by a lot. +#ifndef MICROPY_FLOAT_USE_NATIVE_FLT16 +#define MICROPY_FLOAT_USE_NATIVE_FLT16 (0) +#endif + // Enable arbitrary precision long-int by default. #ifndef MICROPY_LONGINT_IMPL #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) From 9d27183bde0a70521a0d189636e53975df2260eb Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 20 Mar 2024 13:30:22 +1100 Subject: [PATCH 49/49] tests/float/float_struct_e.py: Add specific test for struct 'e' type. Signed-off-by: Damien George --- tests/float/float_struct_e.py | 43 +++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 tests/float/float_struct_e.py diff --git a/tests/float/float_struct_e.py b/tests/float/float_struct_e.py new file mode 100644 index 0000000000..403fbc5db4 --- /dev/null +++ b/tests/float/float_struct_e.py @@ -0,0 +1,43 @@ +# Test struct pack/unpack with 'e' typecode. + +try: + import struct +except ImportError: + print("SKIP") + raise SystemExit + +test_values = ( + 1e-7, + 2e-7, + 1e-6, + 1e-5, + 1e-4, + 1e-3, + 1e-2, + 0.1, + 0, + 1, + 2, + 4, + 8, + 10, + 100, + 1e3, + 1e4, + 6e4, + float("inf"), +) + +for j in test_values: + for i in (j, -j): + x = struct.pack("