From 7497d891a7cfade68d8c7d26b64080c56e0579e5 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Fri, 2 Oct 2020 21:23:09 +0200 Subject: [PATCH 001/179] stm32/sdio: Don't change any DMA2 settings on H7 MCUs. DMA2 clock and registers should be left in their current state in the H7 build. --- ports/stm32/sdio.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ports/stm32/sdio.c b/ports/stm32/sdio.c index 79cc9c9a323a9..87f550e3a6f5d 100644 --- a/ports/stm32/sdio.c +++ b/ports/stm32/sdio.c @@ -76,7 +76,9 @@ void sdio_init(uint32_t irq_pri) { #endif mp_hal_delay_us(10); + #if defined(STM32F7) __HAL_RCC_DMA2_CLK_ENABLE(); // enable DMA2 peripheral + #endif NVIC_SetPriority(SDMMC1_IRQn, irq_pri); @@ -216,7 +218,9 @@ int sdio_transfer(uint32_t cmd, uint32_t arg, uint32_t *resp) { } #endif + #if defined(STM32F7) DMA2_Stream3->CR = 0; // ensure DMA is reset + #endif SDMMC1->ICR = SDMMC_STATIC_FLAGS; // clear interrupts SDMMC1->ARG = arg; SDMMC1->CMD = cmd | SDMMC_CMD_WAITRESP_0 | SDMMC_CMD_CPSMEN; @@ -296,7 +300,9 @@ int sdio_transfer_cmd53(bool write, uint32_t block_size, uint32_t arg, size_t le SDMMC1->DTIMER = 0x2000000; // about 700ms running at 48MHz SDMMC1->DLEN = (len + block_size - 1) & ~(block_size - 1); + #if defined(STM32F7) DMA2_Stream3->CR = 0; + #endif if (dma) { // prepare DMA so it's ready when the DPSM starts its transfer From 9855b9cd82f9d2e9108fd54ab7d4602134ebae1d Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Fri, 2 Oct 2020 21:44:15 +0200 Subject: [PATCH 002/179] stm32/sdcard: Fix H7 build when using SDMMC2. Changes are: - Fix missing IRQ handler when SDMMC2 is used instead of SDMMC1 with H7 MCUs. - Removed outdated H7 series compatibility macros. - Defined common IRQ handler macro for F4 series. --- ports/stm32/sdcard.c | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/ports/stm32/sdcard.c b/ports/stm32/sdcard.c index 9d77722302c99..7d79e9f47b1a8 100644 --- a/ports/stm32/sdcard.c +++ b/ports/stm32/sdcard.c @@ -48,12 +48,14 @@ #if defined(MICROPY_HW_SDMMC2_CK) #define SDIO SDMMC2 +#define SDMMC_IRQHandler SDMMC2_IRQHandler #define SDMMC_CLK_ENABLE() __HAL_RCC_SDMMC2_CLK_ENABLE() #define SDMMC_CLK_DISABLE() __HAL_RCC_SDMMC2_CLK_DISABLE() #define SDMMC_IRQn SDMMC2_IRQn #define SDMMC_DMA dma_SDMMC_2 #else #define SDIO SDMMC1 +#define SDMMC_IRQHandler SDMMC1_IRQHandler #define SDMMC_CLK_ENABLE() __HAL_RCC_SDMMC1_CLK_ENABLE() #define SDMMC_CLK_DISABLE() __HAL_RCC_SDMMC1_CLK_DISABLE() #define SDMMC_IRQn SDMMC1_IRQn @@ -86,8 +88,6 @@ #define SDIO_HARDWARE_FLOW_CONTROL_ENABLE SDMMC_HARDWARE_FLOW_CONTROL_ENABLE #if defined(STM32H7) -#define GPIO_AF12_SDIO GPIO_AF12_SDIO1 -#define SDIO_IRQHandler SDMMC1_IRQHandler #define SDIO_TRANSFER_CLK_DIV SDMMC_NSpeed_CLK_DIV #define SDIO_USE_GPDMA 0 #else @@ -102,6 +102,7 @@ #define SDMMC_CLK_ENABLE() __SDIO_CLK_ENABLE() #define SDMMC_CLK_DISABLE() __SDIO_CLK_DISABLE() #define SDMMC_IRQn SDIO_IRQn +#define SDMMC_IRQHandler SDIO_IRQHandler #define SDMMC_DMA dma_SDIO_0 #define SDIO_USE_GPDMA 1 #define STATIC_AF_SDMMC_CK STATIC_AF_SDIO_CK @@ -398,21 +399,11 @@ STATIC void sdmmc_irq_handler(void) { } } -#if !defined(MICROPY_HW_SDMMC2_CK) -void SDIO_IRQHandler(void) { - IRQ_ENTER(SDIO_IRQn); +void SDMMC_IRQHandler(void) { + IRQ_ENTER(SDMMC_IRQn); sdmmc_irq_handler(); - IRQ_EXIT(SDIO_IRQn); + IRQ_EXIT(SDMMC_IRQn); } -#endif - -#if defined(STM32F7) -void SDMMC2_IRQHandler(void) { - IRQ_ENTER(SDMMC2_IRQn); - sdmmc_irq_handler(); - IRQ_EXIT(SDMMC2_IRQn); -} -#endif STATIC void sdcard_reset_periph(void) { // Fully reset the SDMMC peripheral before calling HAL SD DMA functions. From 7c76a2dfcffa853e73464434861af185b3e5a9f4 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Thu, 1 Oct 2020 14:47:00 +1000 Subject: [PATCH 003/179] stm32/rfcore: Add Python API for basic rfcore operations. The new functions provide FUS/WS status, version and SYS HCI commands: - stm.rfcore_status() - stm.rfcore_fw_version(fw_id) - stm.rfcore_sys_hci(ogf, ocf, cmd) --- ports/stm32/modstm.c | 7 +++ ports/stm32/rfcore.c | 103 +++++++++++++++++++++++++++++++++++-------- ports/stm32/rfcore.h | 4 ++ 3 files changed, 96 insertions(+), 18 deletions(-) diff --git a/ports/stm32/modstm.c b/ports/stm32/modstm.c index 418b8bde2a14e..3f4f33979afd5 100644 --- a/ports/stm32/modstm.c +++ b/ports/stm32/modstm.c @@ -30,6 +30,7 @@ #include "py/obj.h" #include "py/objint.h" #include "extmod/machine_mem.h" +#include "rfcore.h" #include "portmodules.h" #if MICROPY_PY_STM @@ -44,6 +45,12 @@ STATIC const mp_rom_map_elem_t stm_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_mem32), MP_ROM_PTR(&machine_mem32_obj) }, #include "genhdr/modstm_const.h" + + #if defined(STM32WB) + { MP_ROM_QSTR(MP_QSTR_rfcore_status), MP_ROM_PTR(&rfcore_status_obj) }, + { MP_ROM_QSTR(MP_QSTR_rfcore_fw_version), MP_ROM_PTR(&rfcore_fw_version_obj) }, + { MP_ROM_QSTR(MP_QSTR_rfcore_sys_hci), MP_ROM_PTR(&rfcore_sys_hci_obj) }, + #endif }; STATIC MP_DEFINE_CONST_DICT(stm_module_globals, stm_module_globals_table); diff --git a/ports/stm32/rfcore.c b/ports/stm32/rfcore.c index dc23bbe0b2a69..436042cc2ecc6 100644 --- a/ports/stm32/rfcore.c +++ b/ports/stm32/rfcore.c @@ -30,6 +30,7 @@ #include "py/mperrno.h" #include "py/mphal.h" +#include "py/runtime.h" #include "rtc.h" #include "rfcore.h" @@ -66,9 +67,16 @@ #define HCI_EVENT_COMMAND_COMPLETE (0x0E) // -#define SYS_ACK_TIMEOUT_MS (250) +// There can be quite long delays during firmware update. +#define SYS_ACK_TIMEOUT_MS (1000) + #define BLE_ACK_TIMEOUT_MS (250) +// AN5185 +#define MAGIC_FUS_ACTIVE 0xA94656B9 +// AN5289 +#define MAGIC_IPCC_MEM_INCORRECT 0x3DE96F61 + typedef struct _tl_list_node_t { volatile struct _tl_list_node_t *next; volatile struct _tl_list_node_t *prev; @@ -267,10 +275,10 @@ void ipcc_init(uint32_t irq_pri) { /******************************************************************************/ // Transport layer HCI interface -STATIC void tl_parse_hci_msg(const uint8_t *buf, parse_hci_info_t *parse) { +STATIC size_t tl_parse_hci_msg(const uint8_t *buf, parse_hci_info_t *parse) { const char *info; - size_t len = 0; bool applied_set_event_event_mask2_fix = false; + size_t len; switch (buf[0]) { case HCI_KIND_BT_ACL: { info = "HCI_ACL"; @@ -334,6 +342,7 @@ STATIC void tl_parse_hci_msg(const uint8_t *buf, parse_hci_info_t *parse) { } default: info = "HCI_UNKNOWN"; + len = 0; break; } @@ -354,6 +363,8 @@ STATIC void tl_parse_hci_msg(const uint8_t *buf, parse_hci_info_t *parse) { #else (void)info; #endif + + return len; } STATIC void tl_process_msg(volatile tl_list_node_t *head, unsigned int ch, parse_hci_info_t *parse) { @@ -397,7 +408,7 @@ STATIC void tl_check_msg_ble(volatile tl_list_node_t *head, parse_hci_info_t *pa } } -STATIC void tl_hci_cmd(uint8_t *cmd, unsigned int ch, uint8_t hdr, uint16_t opcode, size_t len, const uint8_t *buf) { +STATIC void tl_hci_cmd(uint8_t *cmd, unsigned int ch, uint8_t hdr, uint16_t opcode, const uint8_t *buf, size_t len) { tl_list_node_t *n = (tl_list_node_t *)cmd; n->next = n; n->prev = n; @@ -407,11 +418,19 @@ STATIC void tl_hci_cmd(uint8_t *cmd, unsigned int ch, uint8_t hdr, uint16_t opco cmd[11] = len; memcpy(&cmd[12], buf, len); + #if HCI_TRACE + printf("[% 8d] >HCI(", mp_hal_ticks_ms()); + for (int i = 0; i < len + 4; ++i) { + printf(":%02x", cmd[i + 8]); + } + printf(")\n"); + #endif + // Indicate that this channel is ready. LL_C1_IPCC_SetFlag_CHx(IPCC, ch); } -STATIC int tl_sys_wait_ack(const uint8_t *buf) { +STATIC ssize_t tl_sys_wait_ack(const uint8_t *buf) { uint32_t t0 = mp_hal_ticks_ms(); // C2 will clear this bit to acknowledge the request. @@ -422,14 +441,14 @@ STATIC int tl_sys_wait_ack(const uint8_t *buf) { } } - // C1-to-C2 bit cleared, so process (but ignore) the response. - tl_parse_hci_msg(buf, NULL); - return 0; + // C1-to-C2 bit cleared, so process the response (just get the length, do + // not parse any further). + return (ssize_t)tl_parse_hci_msg(buf, NULL); } -STATIC void tl_sys_hci_cmd_resp(uint16_t opcode, size_t len, const uint8_t *buf) { - tl_hci_cmd(ipcc_membuf_sys_cmd_buf, IPCC_CH_SYS, 0x10, opcode, len, buf); - tl_sys_wait_ack(ipcc_membuf_sys_cmd_buf); +STATIC ssize_t tl_sys_hci_cmd_resp(uint16_t opcode, const uint8_t *buf, size_t len) { + tl_hci_cmd(ipcc_membuf_sys_cmd_buf, IPCC_CH_SYS, 0x10, opcode, buf, len); + return tl_sys_wait_ack(ipcc_membuf_sys_cmd_buf); } STATIC int tl_ble_wait_resp(void) { @@ -447,10 +466,9 @@ STATIC int tl_ble_wait_resp(void) { } // Synchronously send a BLE command. -STATIC void tl_ble_hci_cmd_resp(uint16_t opcode, size_t len, const uint8_t *buf) { - tl_hci_cmd(ipcc_membuf_ble_cmd_buf, IPCC_CH_BLE, HCI_KIND_BT_CMD, opcode, len, buf); +STATIC void tl_ble_hci_cmd_resp(uint16_t opcode, const uint8_t *buf, size_t len) { + tl_hci_cmd(ipcc_membuf_ble_cmd_buf, IPCC_CH_BLE, HCI_KIND_BT_CMD, opcode, buf, len); tl_ble_wait_resp(); - } /******************************************************************************/ @@ -522,8 +540,8 @@ void rfcore_ble_init(void) { tl_check_msg_ble(&ipcc_mem_ble_evt_queue, NULL); // Configure and reset the BLE controller - tl_sys_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_BLE_INIT), sizeof(ble_init_params), (const uint8_t *)&ble_init_params); - tl_ble_hci_cmd_resp(HCI_OPCODE(0x03, 0x0003), 0, NULL); + tl_sys_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_BLE_INIT), (const uint8_t *)&ble_init_params, sizeof(ble_init_params)); + tl_ble_hci_cmd_resp(HCI_OPCODE(0x03, 0x0003), NULL, 0); } void rfcore_ble_hci_cmd(size_t len, const uint8_t *src) { @@ -573,14 +591,14 @@ void rfcore_ble_check_msg(int (*cb)(void *, const uint8_t *, size_t), void *env) SWAP_UINT8(buf[2], buf[7]); SWAP_UINT8(buf[3], buf[6]); SWAP_UINT8(buf[4], buf[5]); - tl_ble_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_WRITE_CONFIG), 8, buf); // set BDADDR + tl_ble_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_WRITE_CONFIG), buf, 8); // set BDADDR } } // "level" is 0x00-0x1f, ranging from -40 dBm to +6 dBm (not linear). void rfcore_ble_set_txpower(uint8_t level) { uint8_t buf[2] = { 0x00, level }; - tl_ble_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_SET_TX_POWER), 2, buf); + tl_ble_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_SET_TX_POWER), buf, 2); } // IPCC IRQ Handlers @@ -605,4 +623,53 @@ void IPCC_C1_RX_IRQHandler(void) { IRQ_EXIT(IPCC_C1_RX_IRQn); } +/******************************************************************************/ +// MicroPython bindings + +STATIC mp_obj_t rfcore_status(void) { + return mp_obj_new_int_from_uint(ipcc_mem_dev_info_tab.fus.table_state); +} +MP_DEFINE_CONST_FUN_OBJ_0(rfcore_status_obj, rfcore_status); + +STATIC mp_obj_t get_version_tuple(uint32_t data) { + mp_obj_t items[] = { + MP_OBJ_NEW_SMALL_INT(data >> 24), MP_OBJ_NEW_SMALL_INT(data >> 16 & 0xFF), MP_OBJ_NEW_SMALL_INT(data >> 8 & 0xFF), MP_OBJ_NEW_SMALL_INT(data >> 4 & 0xF), MP_OBJ_NEW_SMALL_INT(data & 0xF) + }; + return mp_obj_new_tuple(5, items); +} + +STATIC mp_obj_t rfcore_fw_version(mp_obj_t fw_id_in) { + if (ipcc_mem_dev_info_tab.fus.table_state == MAGIC_IPCC_MEM_INCORRECT) { + mp_raise_OSError(MP_EINVAL); + } + mp_int_t fw_id = mp_obj_get_int(fw_id_in); + bool fus_active = ipcc_mem_dev_info_tab.fus.table_state == MAGIC_FUS_ACTIVE; + uint32_t v; + if (fw_id == 0) { + // FUS + v = fus_active ? ipcc_mem_dev_info_tab.fus.fus_version : ipcc_mem_dev_info_tab.ws.fus_version; + } else { + // WS + v = fus_active ? ipcc_mem_dev_info_tab.fus.ws_version : ipcc_mem_dev_info_tab.ws.fw_version; + } + return get_version_tuple(v); +} +MP_DEFINE_CONST_FUN_OBJ_1(rfcore_fw_version_obj, rfcore_fw_version); + +STATIC mp_obj_t rfcore_sys_hci(mp_obj_t ogf_in, mp_obj_t ocf_in, mp_obj_t cmd_in) { + if (ipcc_mem_dev_info_tab.fus.table_state == MAGIC_IPCC_MEM_INCORRECT) { + mp_raise_OSError(MP_EINVAL); + } + mp_int_t ogf = mp_obj_get_int(ogf_in); + mp_int_t ocf = mp_obj_get_int(ocf_in); + mp_buffer_info_t bufinfo = {0}; + mp_get_buffer_raise(cmd_in, &bufinfo, MP_BUFFER_READ); + ssize_t len = tl_sys_hci_cmd_resp(HCI_OPCODE(ogf, ocf), bufinfo.buf, bufinfo.len); + if (len < 0) { + mp_raise_OSError(-len); + } + return mp_obj_new_bytes(ipcc_membuf_sys_cmd_buf, len); +} +MP_DEFINE_CONST_FUN_OBJ_3(rfcore_sys_hci_obj, rfcore_sys_hci); + #endif // defined(STM32WB) diff --git a/ports/stm32/rfcore.h b/ports/stm32/rfcore.h index fbe111e1ebdcd..39f6220a32d26 100644 --- a/ports/stm32/rfcore.h +++ b/ports/stm32/rfcore.h @@ -35,4 +35,8 @@ void rfcore_ble_hci_cmd(size_t len, const uint8_t *src); void rfcore_ble_check_msg(int (*cb)(void *, const uint8_t *, size_t), void *env); void rfcore_ble_set_txpower(uint8_t level); +MP_DECLARE_CONST_FUN_OBJ_0(rfcore_status_obj); +MP_DECLARE_CONST_FUN_OBJ_1(rfcore_fw_version_obj); +MP_DECLARE_CONST_FUN_OBJ_3(rfcore_sys_hci_obj); + #endif // MICROPY_INCLUDED_STM32_RFCORE_H From 222ec1a4a86cdcfa9546c30243e4e244bec9697f Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 29 Sep 2020 18:37:45 +1000 Subject: [PATCH 004/179] stm32/boards/NUCLEO_WB55: Add standalone WB55 FUS/WS firmware updater. This commit adds a script that can be run on-device to install FUS and WS binaries from the filesystem. Instructions for use are provided in the rfcore_firmware.py file. The commit also removes unneeded functionality from the existing rfcore.py debug script (and renames it rfcore_debug.py). --- .../{rfcore.py => rfcore_debug.py} | 119 +--- .../boards/NUCLEO_WB55/rfcore_firmware.py | 547 ++++++++++++++++++ .../boards/NUCLEO_WB55/rfcore_makefirmware.py | 79 +++ 3 files changed, 630 insertions(+), 115 deletions(-) rename ports/stm32/boards/NUCLEO_WB55/{rfcore.py => rfcore_debug.py} (74%) create mode 100644 ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py create mode 100755 ports/stm32/boards/NUCLEO_WB55/rfcore_makefirmware.py diff --git a/ports/stm32/boards/NUCLEO_WB55/rfcore.py b/ports/stm32/boards/NUCLEO_WB55/rfcore_debug.py similarity index 74% rename from ports/stm32/boards/NUCLEO_WB55/rfcore.py rename to ports/stm32/boards/NUCLEO_WB55/rfcore_debug.py index cfe315605e541..4dbead0acc63d 100644 --- a/ports/stm32/boards/NUCLEO_WB55/rfcore.py +++ b/ports/stm32/boards/NUCLEO_WB55/rfcore_debug.py @@ -27,85 +27,16 @@ # mechanism in the WB55, and works with the memory layout configured in # ports/stm32/rfcore.c -- i.e. it expects that rfcore_init() has been run. -# At this stage this is useful for debugging, but can be extended to support -# FUS/WS firmware updates. # e.g. # ../../tools/pyboard.py --device /dev/ttyACM0 boards/NUCLEO_WB55/rfcore.py # to print out SRAM2A, register state and FUS/WS info. +# +# The `stm` module provides some helper functions to access rfcore functionality. +# See rfcore_firmware.py for more information. from machine import mem8, mem16, mem32 -import time, struct import stm - -def addressof(buf): - assert type(buf) is bytearray - return mem32[id(buf) + 12] - - -class Flash: - FLASH_KEY1 = 0x45670123 - FLASH_KEY2 = 0xCDEF89AB - - def wait_not_busy(self): - while mem32[stm.FLASH + stm.FLASH_SR] & 1 << 16: - machine.idle() - - def unlock(self): - mem32[stm.FLASH + stm.FLASH_KEYR] = Flash.FLASH_KEY1 - mem32[stm.FLASH + stm.FLASH_KEYR] = Flash.FLASH_KEY2 - - def lock(self): - mem32[stm.FLASH + stm.FLASH_CR] = 1 << 31 # LOCK - - def erase_page(self, page): - print("erase", page) - assert 0 <= page <= 255 # 1MiB range (4k page) - self.wait_not_busy() - cr = page << 3 | 1 << 1 # PNB # PER - mem32[stm.FLASH + stm.FLASH_CR] = cr - mem32[stm.FLASH + stm.FLASH_CR] = cr | 1 << 16 # STRT - self.wait_not_busy() - mem32[stm.FLASH + stm.FLASH_CR] = 0 - - def write(self, addr, buf): - assert len(buf) % 4 == 0 - self.wait_not_busy() - cr = 1 << 0 # PG - mem32[stm.FLASH + stm.FLASH_CR] = cr - buf_addr = addressof(buf) - off = 0 - while off < len(buf): - mem32[addr + off] = mem32[buf_addr + off] - off += 4 - if off % 8 == 0: - self.wait_not_busy() - if off % 8: - mem32[addr + off] = 0 - self.wait_not_busy() - mem32[stm.FLASH + stm.FLASH_CR] = 0 - - -def copy_file_to_flash(filename, addr): - flash = Flash() - flash.unlock() - try: - with open(filename, "rb") as f: - buf = bytearray(4096) - while 1: - sz = f.readinto(buf) - if sz == 0: - break - print("write", hex(addr), sz) - flash.erase_page((addr - 0x08000000) // 4096) - print("done e") - flash.write(addr, buf) - print("done") - addr += 4096 - finally: - flash.lock() - - SRAM2A_BASE = const(0x2003_0000) # for vendor OGF @@ -205,49 +136,6 @@ def ipcc_init(): print("BLE: 0x%08x 0x%08x 0x%08x" % (BLE_CMD_BUF, BLE_CS_BUF, BLE_EVT_QUEUE)) -def tl_list_init(addr): - mem32[addr] = addr # next - mem32[addr + 4] = addr # prev - - -def tl_list_append(head, n): - sram2a_dump(1024) - print("Appending 0x%08x to 0x%08x" % (head, n)) - # item->next = head - mem32[n] = head - # item->prev = head->prev - mem32[n + 4] = mem32[head + 4] - # head->prev->next = item - mem32[mem32[head + 4]] = n - # head->prev = item - mem32[head + 4] = n - - -def tl_list_unlink(n): - # next = item->next - next = mem32[n] - # prev = item->prev - prev = mem32[n + 4] - # prev->next = item->next - mem32[prev] = next - # item->next->prev = prev - mem32[next + 4] = prev - - return next - - -def tl_list_dump(head): - print( - "list(%08x, %08x, %08x):" % (head, mem32[head] & 0xFFFFFFFF, mem32[head + 4] & 0xFFFFFFFF), - end="", - ) - cur = mem32[head] - while cur != head: - print(" %08x" % (cur & 0xFFFFFFFF), end="") - cur = mem32[cur] - print() - - def fus_active(): return get_ipcc_table_word(TABLE_DEVICE_INFO, 0) == MAGIC_FUS_ACTIVE @@ -346,3 +234,4 @@ def ipcc_state(): ipcc_init() info() dev_info() +ipcc_state() diff --git a/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py b/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py new file mode 100644 index 0000000000000..53c96a552a34c --- /dev/null +++ b/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py @@ -0,0 +1,547 @@ +# This file is part of the MicroPython project, http://micropython.org/ +# +# The MIT License (MIT) +# +# Copyright (c) 2020 Damien P. George +# Copyright (c) 2020 Jim Mussared +# +# 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. + +# This script provides helpers for working with the FUS/WS firmware on the WB55. +# It can be frozen into the MicroPython firmware (via manifest.py) +# +# The current FUS and WS firmware version and state can be queried via the +# `stm` module, e.g. +# stm.rfcore_status() (returns the first word of the device info table) +# stm.rfcore_fw_version(id) (returns a 5-tuple indicating fw version; id is: 0=FUS, 1=WS) +# stm.rfcore_sys_hci(ogf, ocf, cmd_buf) (synchronously execute HCI command on SYS channel) +# +# To perform a firmware update: +# +# 1. Generate "obfuscated" binary images using rfcore_makefirmware.py +# ./boards/NUCLEO_WB55/rfcore_makefirmware.py ~/src/github.com/STMicroelectronics/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x/ /tmp +# This will generate /tmp/{fus_102,fus_110,ws_ble_hci}.bin +# +# 2. Copy required files to the device filesystem. +# In general, it's always safe to copy all three files and the updater will +# figure out what needs to be done. This is the recommended option. +# However, if you already have the latest FUS (1.1.0) installed, then just the +# WS firmware is required. +# If a FUS binary is present, then the existing WS will be removed so it's a good +# idea to always include the WS binary if updating FUS. +# Note that a WS binary will not be installed unless FUS 1.1.0 is installed. +# +# 3. Ensure boot.py calls `rfcore_firmware.resume()`. +# The WB55 will reset several times during the firmware update process, so this +# script manages the update state using RTC backup registers. +# `rfcore_firmware.resume()` will continue the update operation on startup to +# resume any in-progress update operation, and either trigger another reset, or +# return 0 to indicate that the operation completed successfully, or a reason +# code (see REASON_* below) to indicate failure. +# +# 4. Call rfcore_firmware.check_for_updates() to start the update process. +# The device will then immediately reboot and when the firmware update completes, +# the status will be returned from rfcore_firmware.resume(). See the REASON_ codes below. +# You can use the built-in stm.rfcore_fw_version() to query the installed version +# from your application code. + +import struct, os +import machine, stm +from micropython import const + +_OGF_VENDOR = const(0x3F) + +_OCF_FUS_GET_STATE = const(0x52) +_OCF_FUS_FW_UPGRADE = const(0x54) +_OCF_FUS_FW_DELETE = const(0x55) +_OCF_FUS_START_WS = const(0x5A) +_OCF_BLE_INIT = const(0x66) + +_HCI_KIND_VENDOR_RESPONSE = const(0x11) + + +# The firmware updater will search all of flash for the image to install, so +# it's important that the file doesn't exist anywhere on the filesystem and +# that the updater only finds the version that we copy into the reserved area. +# Otherwise it will find matching headers/footers in the flash filesystem and +# get confused leading to either "FUS_STATE_IMG_NOT_AUTHENTIC" or (worse) +# corrupting the FUS. +# See footnote [1] referenced by Table 9 in AN5185 - Rev 4 -- the address +# passed to FUS_FW_UPGRADE is ignored (implying that it must be searching the +# flash). This requires that the firmware files have been pre-processed by +# rfcore_makefirmware.py and this key must match the one there. +_OBFUSCATION_KEY = const(0x0573B55AA) + +# On boards using the internal flash filesystem, this must match the +# `_flash_fs_end` symbol defined by the linker script (boards/stm32wb55xg.ld). +# We erase everything from here until the start of the secure area (defined by +# SFSA) just to ensure that no other fragments of firmware files are left +# behind. On boards with external flash, this just needs to ensure that it +# includes any regions that may contain partial firmware data. +# This is non-const so it can be override. +STAGING_AREA_START = 0x80C0000 + +# First word of device info table indicating FUS state (returned by `stm.rfcore_status()`). +_MAGIC_FUS_ACTIVE = const(0xA94656B9) # AN5185 +_MAGIC_IPCC_MEM_INCORRECT = const(0x3DE96F61) # # AN5185 + +# Argument to `stm.rfcore_fw_version()`. +_FW_VERSION_FUS = const(0) +_FW_VERSION_WS = const(1) + +# No firmware update in progress. Boot normally. +_STATE_IDLE = const(0) + +# A previous firmware update failed. Will return reason code from resume(). +_STATE_FAILED = const(1) + +# Trying to get into the FUS. Keep issuing GET_STATE until the FUS is active. +_STATE_WAITING_FOR_FUS = const(2) + +# Trying to get into the WS. Keep issuing START_WS until the WS is active (or fails). +_STATE_WAITING_FOR_WS = const(3) + +# FW_DELETE has been issued. Waiting for the WS version to report zero. +_STATE_DELETING_WS = const(4) + +# Flash copy has started for FUS/WS. If a reboot occurs, then fail. +_STATE_COPYING_FUS = const(5) +_STATE_COPYING_WS = const(6) + +# Flash write fully completed, ready for install. +_STATE_COPIED_FUS = const(7) +_STATE_COPIED_WS = const(8) + +# Check for next update to perform. +# Either we've just gotten into the FUS, or the first update in a sequence +# has completed. (e.g. FUS done, now do WS). +_STATE_CHECK_UPDATES = const(9) + +# Installation has started, keep polling GET_STATE. +_STATE_INSTALLING_WS = const(10) +_STATE_INSTALLING_FUS = const(11) + +# Update completed successfully. +REASON_OK = const(0) +# The device reset during flash copy. Possibly WS still installed. +REASON_FLASH_COPY_FAILED = const(1) +# Unable to start the WS after firmware update. +REASON_NO_WS = const(2) +# Copying FUS image to staging area caused FUS to fail. +REASON_FLASH_FUS_BAD_STATE = const(3) +# Copying WS image to staging area caused FUS to fail. +REASON_FLASH_WS_BAD_STATE = const(4) +# Cannot get into the FUS. Perhaps rfcore misconfigured. +REASON_FUS_NOT_RESPONDING = const(5) +# After a FUS install, unable to get back to the FUS. +REASON_FUS_NOT_RESPONDING_AFTER_FUS = const(6) +# After a WS install, unable to get back to the FUS. +REASON_FUS_NOT_RESPONDING_AFTER_WS = const(7) +# Unable to query rfcore version/active. +REASON_RFCORE_NOT_CONFIGURED = const(8) +# The WS deletion didn't have any effect. +REASON_WS_STILL_PRESENT = const(9) +# FUS refused to delete the WS. +REASON_WS_DELETION_FAILED = const(10) +# FUS returned a specific code for a FUS update. +# See AN5185 Rev 4, Table 12. Reason between 0x00-0x11 will be added. +REASON_FUS_VENDOR = const(0x10) +# FUS returned a specific code for a WS update. Values as for the FUS update. +REASON_WS_VENDOR = const(0x30) + +# FUS 1.0.2 must be installed before FUS 1.1.0 can be installed. +# A factory Nucleo board has FUS (0, 5, 3, 0, 0) and WS (0, 5, 1, 0, 0). +_FUS_VERSION_102 = (1, 0, 2, 0, 0) +_FUS_VERSION_110 = (1, 1, 0, 0, 0) +_PATH_FUS_102 = "fus_102.bin" +_PATH_FUS_110 = "fus_110.bin" +_PATH_WS_BLE_HCI = "ws_ble_hci.bin" + +# This address is correct for versions up to v1.8 (assuming existing firmware deleted). +# Note any address from the end of the filesystem to the SFSA would be fine, but if +# the FUS is fixed in the future to use the specified address then these are the "correct" +# ones. +_ADDR_FUS = 0x080EC000 +_ADDR_WS_BLE_HCI = 0x080DC000 + + +def log(msg, *args, **kwargs): + print("[rfcore update]", msg.format(*args, **kwargs)) + + +class _Flash: + _FLASH_KEY1 = 0x45670123 + _FLASH_KEY2 = 0xCDEF89AB + + def wait_not_busy(self): + while machine.mem32[stm.FLASH + stm.FLASH_SR] & 1 << 16: + machine.idle() + + def unlock(self): + machine.mem32[stm.FLASH + stm.FLASH_KEYR] = _Flash._FLASH_KEY1 + machine.mem32[stm.FLASH + stm.FLASH_KEYR] = _Flash._FLASH_KEY2 + + def lock(self): + machine.mem32[stm.FLASH + stm.FLASH_CR] = 1 << 31 # LOCK + + def erase_page(self, page): + assert 0 <= page <= 255 # 1MiB range (4k page) + self.wait_not_busy() + cr = page << 3 | 1 << 1 # PNB # PER + machine.mem32[stm.FLASH + stm.FLASH_CR] = cr + machine.mem32[stm.FLASH + stm.FLASH_CR] = cr | 1 << 16 # STRT + self.wait_not_busy() + machine.mem32[stm.FLASH + stm.FLASH_CR] = 0 + + def write(self, addr, buf, sz, key=0): + assert sz % 4 == 0 + self.wait_not_busy() + cr = 1 << 0 # PG + machine.mem32[stm.FLASH + stm.FLASH_CR] = cr + off = 0 + while off < sz: + v = (buf[off]) | (buf[off + 1] << 8) | (buf[off + 2] << 16) | (buf[off + 3] << 24) + machine.mem32[addr + off] = v ^ key + off += 4 + if off % 8 == 0: + self.wait_not_busy() + if off % 8: + machine.mem32[addr + off] = 0 + self.wait_not_busy() + machine.mem32[stm.FLASH + stm.FLASH_CR] = 0 + + +def _copy_file_to_flash(filename, addr): + flash = _Flash() + flash.unlock() + try: + # Erase the entire staging area in flash. + erase_addr = STAGING_AREA_START + sfr_sfsa = machine.mem32[stm.FLASH + stm.FLASH_SFR] & 0xFF + erase_limit = 0x08000000 + sfr_sfsa * 4096 + while erase_addr < erase_limit: + flash.erase_page((erase_addr - 0x08000000) // 4096) + erase_addr += 4096 + + # Write the contents of the firmware (note flash.write will apply the + # XOR de-obfuscation). + with open(filename, "rb") as f: + buf = bytearray(4096) + + while 1: + sz = f.readinto(buf) + if sz == 0: + break + flash.write(addr, buf, sz, _OBFUSCATION_KEY) + addr += 4096 + + finally: + flash.lock() + + +def _parse_vendor_response(data): + assert len(data) >= 7 + assert data[0] == _HCI_KIND_VENDOR_RESPONSE + assert data[1] == 0x0E + # assert data[3] == 0xff # "Num HCI" -- docs say 0xff, but we see 0x01 + op = (data[5] << 8) | data[4] + return (op >> 10, op & 0x3FF, data[6], data[7] if len(data) > 7 else 0) + + +def _run_sys_hci_cmd(ogf, ocf, buf=b""): + try: + ogf_out, ocf_out, status, result = _parse_vendor_response( + stm.rfcore_sys_hci(ogf, ocf, buf) + ) + except OSError: + # Timeout or FUS not active. + return (0xFF, 0xFF) + assert ogf_out == ogf + assert ocf_out == ocf + return (status, result) + + +def fus_get_state(): + return _run_sys_hci_cmd(_OGF_VENDOR, _OCF_FUS_GET_STATE) + + +def fus_is_idle(): + return fus_get_state() == (0, 0) + + +def fus_start_ws(): + return _run_sys_hci_cmd(_OGF_VENDOR, _OCF_FUS_START_WS) + + +def _fus_fwdelete(): + return _run_sys_hci_cmd(_OGF_VENDOR, _OCF_FUS_FW_DELETE) + + +def _fus_run_fwupgrade(addr): + # Note: Address is ignored by the FUS (see comments above). + return _run_sys_hci_cmd(_OGF_VENDOR, _OCF_FUS_FW_UPGRADE, struct.pack(" FUS 1.1.0 -> WS (depending on what's available). + elif _STATE_id == _STATE_CHECK_UPDATES: + log("Checking for updates") + fus_version = stm.rfcore_fw_version(_FW_VERSION_FUS) + log("FUS version {}", fus_version) + if fus_version < _FUS_VERSION_102: + log("Factory FUS detected") + if _stat_and_start_copy( + _PATH_FUS_102, _ADDR_FUS, _STATE_COPYING_FUS, _STATE_COPIED_FUS + ): + continue + elif fus_version >= _FUS_VERSION_102 and fus_version < _FUS_VERSION_110: + log("FUS 1.0.2 detected") + if _stat_and_start_copy( + _PATH_FUS_110, _ADDR_FUS, _STATE_COPYING_FUS, _STATE_COPIED_FUS + ): + continue + else: + log("FUS is up-to-date") + + if fus_version >= _FUS_VERSION_110: + if _stat_and_start_copy( + _PATH_WS_BLE_HCI, _ADDR_WS_BLE_HCI, _STATE_COPYING_WS, _STATE_COPIED_WS + ): + continue + else: + log("No WS updates available") + else: + # Don't attempt to install WS if we're running an old FUS. + log("Need latest FUS to install WS") + + # Attempt to go back to WS. + # Either this will fail (because WS was removed due to FUS install), or + # this whole thing was a no-op and we should be fine to restart WS. + _write_state(_STATE_WAITING_FOR_WS) + + # This shouldn't happen - the flash write should always complete and + # move straight onto the COPIED state. Failure here indicates that + # the rfcore is misconfigured or the WS firmware was not deleted first. + elif _STATE_id == _STATE_COPYING_FUS or _STATE_id == _STATE_COPYING_WS: + log("Flash copy failed mid-write") + _write_failure_state(REASON_FLASH_COPY_FAILED) + + # Flash write completed, we should immediately see GET_STATE return 0,0 + # so we can start the FUS install. + elif _STATE_id == _STATE_COPIED_FUS: + if fus_is_idle(): + log("FUS copy complete, installing") + _write_state(_STATE_INSTALLING_FUS) + _fus_run_fwupgrade(_ADDR_FUS) + else: + log("FUS copy bad state") + _write_failure_state(REASON_FLASH_FUS_BAD_STATE) + + # Keep polling the state until we see a 0,0 (success) or non-transient + # error. In general we should expect to see (16,0) several times, + # followed by a (255,0), followed by (0, 0). + elif _STATE_id == _STATE_INSTALLING_FUS: + log("Installing FUS...") + status, result = fus_get_state() + log("FUS state: {} {}", status, result) + if 0x20 <= status <= 0x2F and result == 0: + # FUS_STATE_FUS_UPGRD_ONGOING + log("FUS still in progress...") + elif 0x10 <= status <= 0x1F and result == 0x11: + # FUS_STATE_FW_UPGRD_ONGOING and FUS_FW_ROLLBACK_ERROR + # Confusingly this is a "FW_UPGRD" (0x10) not "FUS_UPRD" (0x20). + log("Attempted to install same FUS version... re-querying FUS state to resume.") + elif status == 0: + log("FUS update successful") + _write_state(_STATE_CHECK_UPDATES) + # Need to force a reset after FUS install otherwise a subsequent flash copy will fail. + machine.reset() + elif result == 0: + # See below (for equivalent path for WS install -- we + # sometimes see (255,0) right at the end). + log("Re-querying FUS state...") + elif result == 0xFF: + _write_failure_state(REASON_FUS_NOT_RESPONDING_AFTER_FUS) + else: + _write_failure_state(REASON_FUS_VENDOR + result) + + # Keep polling the state until we see 0,0 or failure (1,0). Any other + # result means retry (but the docs say that 0 and 1 are the only + # status values). + elif _STATE_id == _STATE_DELETING_WS: + log("Deleting WS...") + status, result = fus_get_state() + log("FUS state: {} {}", status, result) + if status == 0: + if sum(stm.rfcore_fw_version(_FW_VERSION_WS)) == 0: + log("WS deletion complete") + _write_state(_STATE_CHECK_UPDATES) + else: + log("WS deletion no effect") + _write_failure_state(REASON_WS_STILL_PRESENT) + elif status == 1: + log("WS deletion failed") + _write_failure_state(REASON_WS_DELETION_FAILED) + + # As for _STATE_COPIED_FUS above. We should immediately see 0,0. + elif _STATE_id == _STATE_COPIED_WS: + if fus_is_idle(): + log("WS copy complete, installing") + _write_state(_STATE_INSTALLING_WS) + _fus_run_fwupgrade(_ADDR_WS_BLE_HCI) + else: + log("WS copy bad state") + _write_failure_state(REASON_FLASH_WS_BAD_STATE) + + # As for _STATE_INSTALLING_FUS above. + elif _STATE_id == _STATE_INSTALLING_WS: + log("Installing WS...") + status, result = fus_get_state() + log("FUS state: {} {}", status, result) + if 0x10 <= status <= 0x1F and result == 0: + # FUS_STATE_FW_UPGRD_ONGOING + log("WS still in progress...") + elif 0x10 <= status <= 0x1F and result == 0x11: + # FUS_FW_ROLLBACK_ERROR + log("Attempted to install same WS version... re-querying FUS state to resume.") + elif status == 0: + log("WS update successful") + _write_state(_STATE_WAITING_FOR_WS) + elif result == 0: + # We get a error response with no payload sometimes at the end + # of the update (this is not in AN5185). Re-try the GET_STATE. + # The same thing happens transitioning from WS to FUS mode. + # The actual HCI response has no payload, the result=0 comes from + # _parse_vendor_response above when len=7. + log("Re-querying FUS state...") + elif result == 0xFF: + # This is specifically a failure sending the HCI command. + _write_failure_state(REASON_FUS_NOT_RESPONDING_AFTER_WS) + else: + _write_failure_state(REASON_WS_VENDOR + result) + + +# Start a firmware update. +# This will immediately trigger a reset and start the update process on boot. +def check_for_updates(): + log("Starting firmware update") + _write_state(_STATE_WAITING_FOR_FUS) + machine.reset() diff --git a/ports/stm32/boards/NUCLEO_WB55/rfcore_makefirmware.py b/ports/stm32/boards/NUCLEO_WB55/rfcore_makefirmware.py new file mode 100755 index 0000000000000..23f3d20f0c9ce --- /dev/null +++ b/ports/stm32/boards/NUCLEO_WB55/rfcore_makefirmware.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# +# This file is part of the MicroPython project, http://micropython.org/ +# +# The MIT License (MIT) +# +# Copyright (c) 2020 Jim Mussared +# +# 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. + +# This script obfuscates the ST wireless binaries so they can be safely copied +# to the flash filesystem and not be accidentally discovered by the FUS during +# an update. See more information (and the corresponding de-obfuscation) in +# rfcore_firmware.py as well as instructions on how to use. + +import os +import struct +import sys + +# Must match rfcore_firmware.py. +_OBFUSCATION_KEY = 0x0573B55AA + +_FIRMWARE_FILES = { + "stm32wb5x_FUS_fw_1_0_2.bin": "fus_102.bin", + "stm32wb5x_FUS_fw.bin": "fus_110.bin", + "stm32wb5x_BLE_HCILayer_fw.bin": "ws_ble_hci.bin", +} + + +def main(src_path, dest_path): + for src_file, dest_file in _FIRMWARE_FILES.items(): + src_file = os.path.join(src_path, src_file) + dest_file = os.path.join(dest_path, dest_file) + if not os.path.exists(src_file): + print("Unable to find: {}".format(src_file)) + continue + sz = 0 + with open(src_file, "rb") as src: + with open(dest_file, "wb") as dest: + while True: + b = src.read(4) + if not b: + break + (v,) = struct.unpack(" Date: Fri, 9 Oct 2020 16:51:47 +1100 Subject: [PATCH 005/179] stm32/rfcore: Update to support WS=1.9.0.0.4. This WS update to 1.9.0.0.4 broke the workaround used in rfcore for OCF_CB_SET_EVENT_MASK2, so fix it to support WS 1.8 and 1.9. --- ports/stm32/rfcore.c | 45 +++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/ports/stm32/rfcore.c b/ports/stm32/rfcore.c index 436042cc2ecc6..3850a17dae642 100644 --- a/ports/stm32/rfcore.c +++ b/ports/stm32/rfcore.c @@ -66,6 +66,7 @@ #define HCI_KIND_VENDOR_EVENT (0x12) #define HCI_EVENT_COMMAND_COMPLETE (0x0E) // +#define HCI_EVENT_COMMAND_STATUS (0x0F) // // There can be quite long delays during firmware update. #define SYS_ACK_TIMEOUT_MS (1000) @@ -275,9 +276,19 @@ void ipcc_init(uint32_t irq_pri) { /******************************************************************************/ // Transport layer HCI interface +// The WS firmware doesn't support OCF_CB_SET_EVENT_MASK2, and fails with: +// v1.8.0.0.4 (and below): HCI_EVENT_COMMAND_COMPLETE with a non-zero status +// v1.9.0.0.4 (and above): HCI_EVENT_COMMAND_STATUS with a non-zero status +// In either case we detect the failure response and inject this response +// instead (which is HCI_EVENT_COMMAND_COMPLETE for OCF_CB_SET_EVENT_MASK2 +// with status=0). +STATIC const uint8_t set_event_event_mask2_fix_payload[] = { 0x04, 0x0e, 0x04, 0x01, 0x63, 0x0c, 0x00 }; + STATIC size_t tl_parse_hci_msg(const uint8_t *buf, parse_hci_info_t *parse) { const char *info; - bool applied_set_event_event_mask2_fix = false; + #if HCI_TRACE + int applied_set_event_event_mask2_fix = 0; + #endif size_t len; switch (buf[0]) { case HCI_KIND_BT_ACL: { @@ -300,10 +311,12 @@ STATIC size_t tl_parse_hci_msg(const uint8_t *buf, parse_hci_info_t *parse) { uint8_t status = buf[6]; if (opcode == HCI_OPCODE(OGF_CTLR_BASEBAND, OCF_CB_SET_EVENT_MASK2) && status != 0) { - // The WB doesn't support this command (despite being in CS 4.1), so pretend like - // it succeeded by replacing the final byte (status) with a zero. - applied_set_event_event_mask2_fix = true; - len -= 1; + // For WS firmware v1.8.0.0.4 and below. Reply with the "everything OK" payload. + parse->cb_fun(parse->cb_env, set_event_event_mask2_fix_payload, sizeof(set_event_event_mask2_fix_payload)); + #if HCI_TRACE + applied_set_event_event_mask2_fix = 18; + #endif + break; // Don't send the original payload. } if (opcode == HCI_OPCODE(OGF_CTLR_BASEBAND, OCF_CB_RESET) && status == 0) { @@ -313,15 +326,21 @@ STATIC size_t tl_parse_hci_msg(const uint8_t *buf, parse_hci_info_t *parse) { } } - parse->cb_fun(parse->cb_env, buf, len); + if (buf[1] == HCI_EVENT_COMMAND_STATUS && len == 7) { + uint16_t opcode = (buf[6] << 8) | buf[5]; + uint8_t status = buf[3]; - if (applied_set_event_event_mask2_fix) { - // Inject the zero status. - uint8_t data = 0; - parse->cb_fun(parse->cb_env, &data, 1); - // Restore the length for the HCI tracing below. - len += 1; + if (opcode == HCI_OPCODE(OGF_CTLR_BASEBAND, OCF_CB_SET_EVENT_MASK2) && status != 0) { + // For WS firmware v1.9.0.0.4 and higher. Reply with the "everything OK" payload. + parse->cb_fun(parse->cb_env, set_event_event_mask2_fix_payload, sizeof(set_event_event_mask2_fix_payload)); + #if HCI_TRACE + applied_set_event_event_mask2_fix = 19; + #endif + break; // Don't send the original payload. + } } + + parse->cb_fun(parse->cb_env, buf, len); } break; } @@ -356,7 +375,7 @@ STATIC size_t tl_parse_hci_msg(const uint8_t *buf, parse_hci_info_t *parse) { printf(" (reset)"); } if (applied_set_event_event_mask2_fix) { - printf(" (mask2 fix)"); + printf(" (mask2 fix %d)", applied_set_event_event_mask2_fix); } printf("\n"); From 880875bea1d825b8fa0d1c4a779ff767377f7655 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 9 Oct 2020 17:10:29 +1100 Subject: [PATCH 006/179] py/objdict: Add mp_const_empty_dict_obj, use it for mp_const_empty_map. --- py/map.c | 11 ----------- py/obj.h | 9 ++++++--- py/objdict.c | 12 ++++++++++++ 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/py/map.c b/py/map.c index 676c364da76a1..54f4b0204b87f 100644 --- a/py/map.c +++ b/py/map.c @@ -40,17 +40,6 @@ #define DEBUG_printf(...) (void)0 #endif -// Fixed empty map. Useful when need to call kw-receiving functions -// without any keywords from C, etc. -const mp_map_t mp_const_empty_map = { - .all_keys_are_qstrs = 0, - .is_fixed = 1, - .is_ordered = 1, - .used = 0, - .alloc = 0, - .table = NULL, -}; - // This table of sizes is used to control the growth of hash tables. // The first set of sizes are chosen so the allocation fits exactly in a // 4-word GC block, and it's not so important for these small values to be diff --git a/py/obj.h b/py/obj.h index 31542a7f9bd7c..6a040b77739c2 100644 --- a/py/obj.h +++ b/py/obj.h @@ -422,8 +422,6 @@ typedef enum _mp_map_lookup_kind_t { MP_MAP_LOOKUP_ADD_IF_NOT_FOUND_OR_REMOVE_IF_FOUND = 3, // only valid for mp_set_lookup } mp_map_lookup_kind_t; -extern const mp_map_t mp_const_empty_map; - static inline bool mp_map_slot_is_filled(const mp_map_t *map, size_t pos) { assert(pos < map->alloc); return (map)->table[pos].key != MP_OBJ_NULL && (map)->table[pos].key != MP_OBJ_SENTINEL; @@ -685,17 +683,22 @@ extern const struct _mp_obj_bool_t mp_const_false_obj; extern const struct _mp_obj_bool_t mp_const_true_obj; #endif -// Constant objects, globally accessible: b'', (), Ellipsis, NotImplemented, GeneratorExit() +// Constant objects, globally accessible: b'', (), {}, Ellipsis, NotImplemented, GeneratorExit() // The below macros are for convenience only. #define mp_const_empty_bytes (MP_OBJ_FROM_PTR(&mp_const_empty_bytes_obj)) #define mp_const_empty_tuple (MP_OBJ_FROM_PTR(&mp_const_empty_tuple_obj)) #define mp_const_notimplemented (MP_OBJ_FROM_PTR(&mp_const_notimplemented_obj)) extern const struct _mp_obj_str_t mp_const_empty_bytes_obj; extern const struct _mp_obj_tuple_t mp_const_empty_tuple_obj; +extern const struct _mp_obj_dict_t mp_const_empty_dict_obj; extern const struct _mp_obj_singleton_t mp_const_ellipsis_obj; extern const struct _mp_obj_singleton_t mp_const_notimplemented_obj; extern const struct _mp_obj_exception_t mp_const_GeneratorExit_obj; +// Fixed empty map. Useful when calling keyword-receiving functions +// without any keywords from C, etc. +#define mp_const_empty_map (mp_const_empty_dict_obj.map) + // General API for objects // These macros are derived from more primitive ones and are used to diff --git a/py/objdict.c b/py/objdict.c index 4fa59f4634cb0..4e51f259e7ff4 100644 --- a/py/objdict.c +++ b/py/objdict.c @@ -33,6 +33,18 @@ #include "py/objtype.h" #include "py/objstr.h" +const mp_obj_dict_t mp_const_empty_dict_obj = { + .base = { .type = &mp_type_dict }, + .map = { + .all_keys_are_qstrs = 0, + .is_fixed = 1, + .is_ordered = 1, + .used = 0, + .alloc = 0, + .table = NULL, + } +}; + STATIC mp_obj_t dict_update(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs); // This is a helper function to iterate through a dictionary. The state of From b137d064e9e0bfebd2a59a9b312935031252e742 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 4 Aug 2020 14:28:06 +1000 Subject: [PATCH 007/179] py/objtype: Handle __dict__ attribute when type has no locals. --- py/objtype.c | 9 ++++++--- tests/basics/class_dict.py | 5 +++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/py/objtype.c b/py/objtype.c index 40900dc050358..7f75232941046 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -1014,13 +1014,16 @@ STATIC void type_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { if (attr == MP_QSTR___dict__) { // Returns a read-only dict of the class attributes. // If the internal locals is not fixed, a copy will be created. - mp_obj_dict_t *dict = self->locals_dict; + const mp_obj_dict_t *dict = self->locals_dict; + if (!dict) { + dict = &mp_const_empty_dict_obj; + } if (dict->map.is_fixed) { dest[0] = MP_OBJ_FROM_PTR(dict); } else { dest[0] = mp_obj_dict_copy(MP_OBJ_FROM_PTR(dict)); - dict = MP_OBJ_TO_PTR(dest[0]); - dict->map.is_fixed = 1; + mp_obj_dict_t *dict_copy = MP_OBJ_TO_PTR(dest[0]); + dict_copy->map.is_fixed = 1; } return; } diff --git a/tests/basics/class_dict.py b/tests/basics/class_dict.py index f80ded678b0f8..508ae5e2e543e 100644 --- a/tests/basics/class_dict.py +++ b/tests/basics/class_dict.py @@ -17,3 +17,8 @@ class Foo: d = Foo.__dict__ print(d["a"], d["b"]) + + +# dict of a class that has no locals_dict (return empty dict). +d = type(type('')).__dict__ +print(d is not None) From 520bb88d70893287b2c2728259cd020ddc710eaf Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Thu, 15 Oct 2020 20:09:04 +1100 Subject: [PATCH 008/179] stm32/boards/NUCLEO_WB55/rfcore_firmware.py: Fix flash unlock. The flash can sometimes be in an already-unlocked state, and attempting to unlock it again will cause an immediate reset. So make _Flash.unlock() check FLASH_CR_LOCK to get the current state. Also fix some magic numbers for FLASH_CR_LOCK AND FLASH_CR_STRT. The machine.reset() could be removed because it no longer crashes now that the flash unlock is fixed. Signed-off-by: Jim Mussared --- .../boards/NUCLEO_WB55/rfcore_firmware.py | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py b/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py index 53c96a552a34c..de98d97430445 100644 --- a/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py +++ b/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py @@ -189,23 +189,31 @@ class _Flash: _FLASH_KEY1 = 0x45670123 _FLASH_KEY2 = 0xCDEF89AB + _FLASH_CR_STRT_MASK = 1 << 16 + _FLASH_CR_LOCK_MASK = 1 << 31 + _FLASH_SR_BSY_MASK = 1 << 16 + def wait_not_busy(self): - while machine.mem32[stm.FLASH + stm.FLASH_SR] & 1 << 16: + while machine.mem32[stm.FLASH + stm.FLASH_SR] & _Flash._FLASH_SR_BSY_MASK: machine.idle() def unlock(self): - machine.mem32[stm.FLASH + stm.FLASH_KEYR] = _Flash._FLASH_KEY1 - machine.mem32[stm.FLASH + stm.FLASH_KEYR] = _Flash._FLASH_KEY2 + if machine.mem32[stm.FLASH + stm.FLASH_CR] & _Flash._FLASH_CR_LOCK_MASK: + # Only unlock if already locked (i.e. FLASH_CR_LOCK is set). + machine.mem32[stm.FLASH + stm.FLASH_KEYR] = _Flash._FLASH_KEY1 + machine.mem32[stm.FLASH + stm.FLASH_KEYR] = _Flash._FLASH_KEY2 + else: + log("Flash was already unlocked.") def lock(self): - machine.mem32[stm.FLASH + stm.FLASH_CR] = 1 << 31 # LOCK + machine.mem32[stm.FLASH + stm.FLASH_CR] = _Flash._FLASH_CR_LOCK_MASK def erase_page(self, page): assert 0 <= page <= 255 # 1MiB range (4k page) self.wait_not_busy() cr = page << 3 | 1 << 1 # PNB # PER machine.mem32[stm.FLASH + stm.FLASH_CR] = cr - machine.mem32[stm.FLASH + stm.FLASH_CR] = cr | 1 << 16 # STRT + machine.mem32[stm.FLASH + stm.FLASH_CR] = cr | _Flash._FLASH_CR_STRT_MASK self.wait_not_busy() machine.mem32[stm.FLASH + stm.FLASH_CR] = 0 @@ -472,8 +480,6 @@ def resume(): elif status == 0: log("FUS update successful") _write_state(_STATE_CHECK_UPDATES) - # Need to force a reset after FUS install otherwise a subsequent flash copy will fail. - machine.reset() elif result == 0: # See below (for equivalent path for WS install -- we # sometimes see (255,0) right at the end). From dfb63b56134dc054b2e0023d9a404dea749d1fee Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Thu, 15 Oct 2020 20:11:43 +1100 Subject: [PATCH 009/179] stm32/boards/NUCLEO_WB55/rfcore_firmware.py: Fix bad variable name. Signed-off-by: Jim Mussared --- .../boards/NUCLEO_WB55/rfcore_firmware.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py b/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py index de98d97430445..3358b246d064c 100644 --- a/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py +++ b/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py @@ -370,18 +370,18 @@ def resume(): return _write_failure_state(REASON_RFCORE_NOT_CONFIGURED) while True: - _STATE_id = _read_state() + state = _read_state() - if _STATE_id == _STATE_IDLE: + if state == _STATE_IDLE: log("Firmware update complete") return 0 - elif _STATE_id == _STATE_FAILED: + elif state == _STATE_FAILED: log("Firmware update failed") return _read_failure_reason() # Keep calling GET_STATE until error or FUS. - elif _STATE_id == _STATE_WAITING_FOR_FUS: + elif state == _STATE_WAITING_FOR_FUS: log("Querying FUS state") status, result = fus_get_state() log("FUS state: {} {}", status, result) @@ -395,7 +395,7 @@ def resume(): _write_state(_STATE_CHECK_UPDATES) # Keep trying to start the WS until !fus_active() (or error). - elif _STATE_id == _STATE_WAITING_FOR_WS: + elif state == _STATE_WAITING_FOR_WS: if stm.rfcore_status() != _MAGIC_FUS_ACTIVE: log("WS active") _write_state(_STATE_IDLE) @@ -410,7 +410,7 @@ def resume(): _write_failure_state(REASON_NO_WS) # Sequence the FUS 1.0.2 -> FUS 1.1.0 -> WS (depending on what's available). - elif _STATE_id == _STATE_CHECK_UPDATES: + elif state == _STATE_CHECK_UPDATES: log("Checking for updates") fus_version = stm.rfcore_fw_version(_FW_VERSION_FUS) log("FUS version {}", fus_version) @@ -448,13 +448,13 @@ def resume(): # This shouldn't happen - the flash write should always complete and # move straight onto the COPIED state. Failure here indicates that # the rfcore is misconfigured or the WS firmware was not deleted first. - elif _STATE_id == _STATE_COPYING_FUS or _STATE_id == _STATE_COPYING_WS: + elif state == _STATE_COPYING_FUS or state == _STATE_COPYING_WS: log("Flash copy failed mid-write") _write_failure_state(REASON_FLASH_COPY_FAILED) # Flash write completed, we should immediately see GET_STATE return 0,0 # so we can start the FUS install. - elif _STATE_id == _STATE_COPIED_FUS: + elif state == _STATE_COPIED_FUS: if fus_is_idle(): log("FUS copy complete, installing") _write_state(_STATE_INSTALLING_FUS) @@ -466,7 +466,7 @@ def resume(): # Keep polling the state until we see a 0,0 (success) or non-transient # error. In general we should expect to see (16,0) several times, # followed by a (255,0), followed by (0, 0). - elif _STATE_id == _STATE_INSTALLING_FUS: + elif state == _STATE_INSTALLING_FUS: log("Installing FUS...") status, result = fus_get_state() log("FUS state: {} {}", status, result) @@ -492,7 +492,7 @@ def resume(): # Keep polling the state until we see 0,0 or failure (1,0). Any other # result means retry (but the docs say that 0 and 1 are the only # status values). - elif _STATE_id == _STATE_DELETING_WS: + elif state == _STATE_DELETING_WS: log("Deleting WS...") status, result = fus_get_state() log("FUS state: {} {}", status, result) @@ -508,7 +508,7 @@ def resume(): _write_failure_state(REASON_WS_DELETION_FAILED) # As for _STATE_COPIED_FUS above. We should immediately see 0,0. - elif _STATE_id == _STATE_COPIED_WS: + elif state == _STATE_COPIED_WS: if fus_is_idle(): log("WS copy complete, installing") _write_state(_STATE_INSTALLING_WS) @@ -518,7 +518,7 @@ def resume(): _write_failure_state(REASON_FLASH_WS_BAD_STATE) # As for _STATE_INSTALLING_FUS above. - elif _STATE_id == _STATE_INSTALLING_WS: + elif state == _STATE_INSTALLING_WS: log("Installing WS...") status, result = fus_get_state() log("FUS state: {} {}", status, result) From 893f75546c4e65ca5b72bc7ef9b91003372c4705 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Thu, 15 Oct 2020 20:35:11 +1100 Subject: [PATCH 010/179] stm32/boards/NUCLEO_WB55/rfcore_firmware.py: Increase GET_STATE timeout. When installing WS firmware, the very first GET_STATE can take several seconds to respond (especially with the larger binaries like BLE_stack_full). Allows stm.rfcore_sys_hci to take an optional timeout, defaulting to SYS_ACK_TIMEOUT_MS (which is 250ms). Signed-off-by: Jim Mussared --- .../boards/NUCLEO_WB55/rfcore_firmware.py | 18 +++++++---- ports/stm32/rfcore.c | 32 +++++++++++-------- ports/stm32/rfcore.h | 2 +- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py b/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py index 3358b246d064c..b5f1d0072e349 100644 --- a/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py +++ b/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py @@ -180,6 +180,12 @@ _ADDR_FUS = 0x080EC000 _ADDR_WS_BLE_HCI = 0x080DC000 +# When installing the FUS/WS it can take a long time to return to the first +# GET_STATE HCI command. +# e.g. Installing stm32wb5x_BLE_Stack_full_fw.bin takes 3600ms to respond. +_INSTALLING_FUS_GET_STATE_TIMEOUT = const(1000) +_INSTALLING_WS_GET_STATE_TIMEOUT = const(6000) + def log(msg, *args, **kwargs): print("[rfcore update]", msg.format(*args, **kwargs)) @@ -272,10 +278,10 @@ def _parse_vendor_response(data): return (op >> 10, op & 0x3FF, data[6], data[7] if len(data) > 7 else 0) -def _run_sys_hci_cmd(ogf, ocf, buf=b""): +def _run_sys_hci_cmd(ogf, ocf, buf=b"", timeout=0): try: ogf_out, ocf_out, status, result = _parse_vendor_response( - stm.rfcore_sys_hci(ogf, ocf, buf) + stm.rfcore_sys_hci(ogf, ocf, buf, timeout) ) except OSError: # Timeout or FUS not active. @@ -285,8 +291,8 @@ def _run_sys_hci_cmd(ogf, ocf, buf=b""): return (status, result) -def fus_get_state(): - return _run_sys_hci_cmd(_OGF_VENDOR, _OCF_FUS_GET_STATE) +def fus_get_state(timeout=0): + return _run_sys_hci_cmd(_OGF_VENDOR, _OCF_FUS_GET_STATE, timeout=timeout) def fus_is_idle(): @@ -468,7 +474,7 @@ def resume(): # followed by a (255,0), followed by (0, 0). elif state == _STATE_INSTALLING_FUS: log("Installing FUS...") - status, result = fus_get_state() + status, result = fus_get_state(_INSTALLING_FUS_GET_STATE_TIMEOUT) log("FUS state: {} {}", status, result) if 0x20 <= status <= 0x2F and result == 0: # FUS_STATE_FUS_UPGRD_ONGOING @@ -520,7 +526,7 @@ def resume(): # As for _STATE_INSTALLING_FUS above. elif state == _STATE_INSTALLING_WS: log("Installing WS...") - status, result = fus_get_state() + status, result = fus_get_state(_INSTALLING_WS_GET_STATE_TIMEOUT) log("FUS state: {} {}", status, result) if 0x10 <= status <= 0x1F and result == 0: # FUS_STATE_FW_UPGRD_ONGOING diff --git a/ports/stm32/rfcore.c b/ports/stm32/rfcore.c index 3850a17dae642..1fc0c9531d03b 100644 --- a/ports/stm32/rfcore.c +++ b/ports/stm32/rfcore.c @@ -68,9 +68,7 @@ #define HCI_EVENT_COMMAND_COMPLETE (0x0E) // #define HCI_EVENT_COMMAND_STATUS (0x0F) // -// There can be quite long delays during firmware update. -#define SYS_ACK_TIMEOUT_MS (1000) - +#define SYS_ACK_TIMEOUT_MS (250) #define BLE_ACK_TIMEOUT_MS (250) // AN5185 @@ -449,12 +447,14 @@ STATIC void tl_hci_cmd(uint8_t *cmd, unsigned int ch, uint8_t hdr, uint16_t opco LL_C1_IPCC_SetFlag_CHx(IPCC, ch); } -STATIC ssize_t tl_sys_wait_ack(const uint8_t *buf) { +STATIC ssize_t tl_sys_wait_ack(const uint8_t *buf, mp_int_t timeout_ms) { uint32_t t0 = mp_hal_ticks_ms(); + timeout_ms = MAX(SYS_ACK_TIMEOUT_MS, timeout_ms); + // C2 will clear this bit to acknowledge the request. while (LL_C1_IPCC_IsActiveFlag_CHx(IPCC, IPCC_CH_SYS)) { - if (mp_hal_ticks_ms() - t0 > SYS_ACK_TIMEOUT_MS) { + if (mp_hal_ticks_ms() - t0 > timeout_ms) { printf("tl_sys_wait_ack: timeout\n"); return -MP_ETIMEDOUT; } @@ -465,9 +465,9 @@ STATIC ssize_t tl_sys_wait_ack(const uint8_t *buf) { return (ssize_t)tl_parse_hci_msg(buf, NULL); } -STATIC ssize_t tl_sys_hci_cmd_resp(uint16_t opcode, const uint8_t *buf, size_t len) { +STATIC ssize_t tl_sys_hci_cmd_resp(uint16_t opcode, const uint8_t *buf, size_t len, mp_int_t timeout_ms) { tl_hci_cmd(ipcc_membuf_sys_cmd_buf, IPCC_CH_SYS, 0x10, opcode, buf, len); - return tl_sys_wait_ack(ipcc_membuf_sys_cmd_buf); + return tl_sys_wait_ack(ipcc_membuf_sys_cmd_buf, timeout_ms); } STATIC int tl_ble_wait_resp(void) { @@ -559,7 +559,7 @@ void rfcore_ble_init(void) { tl_check_msg_ble(&ipcc_mem_ble_evt_queue, NULL); // Configure and reset the BLE controller - tl_sys_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_BLE_INIT), (const uint8_t *)&ble_init_params, sizeof(ble_init_params)); + tl_sys_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_BLE_INIT), (const uint8_t *)&ble_init_params, sizeof(ble_init_params), 0); tl_ble_hci_cmd_resp(HCI_OPCODE(0x03, 0x0003), NULL, 0); } @@ -675,20 +675,24 @@ STATIC mp_obj_t rfcore_fw_version(mp_obj_t fw_id_in) { } MP_DEFINE_CONST_FUN_OBJ_1(rfcore_fw_version_obj, rfcore_fw_version); -STATIC mp_obj_t rfcore_sys_hci(mp_obj_t ogf_in, mp_obj_t ocf_in, mp_obj_t cmd_in) { +STATIC mp_obj_t rfcore_sys_hci(size_t n_args, const mp_obj_t *args) { if (ipcc_mem_dev_info_tab.fus.table_state == MAGIC_IPCC_MEM_INCORRECT) { mp_raise_OSError(MP_EINVAL); } - mp_int_t ogf = mp_obj_get_int(ogf_in); - mp_int_t ocf = mp_obj_get_int(ocf_in); + mp_int_t ogf = mp_obj_get_int(args[0]); + mp_int_t ocf = mp_obj_get_int(args[1]); mp_buffer_info_t bufinfo = {0}; - mp_get_buffer_raise(cmd_in, &bufinfo, MP_BUFFER_READ); - ssize_t len = tl_sys_hci_cmd_resp(HCI_OPCODE(ogf, ocf), bufinfo.buf, bufinfo.len); + mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ); + mp_int_t timeout_ms = 0; + if (n_args >= 4) { + timeout_ms = mp_obj_get_int(args[3]); + } + ssize_t len = tl_sys_hci_cmd_resp(HCI_OPCODE(ogf, ocf), bufinfo.buf, bufinfo.len, timeout_ms); if (len < 0) { mp_raise_OSError(-len); } return mp_obj_new_bytes(ipcc_membuf_sys_cmd_buf, len); } -MP_DEFINE_CONST_FUN_OBJ_3(rfcore_sys_hci_obj, rfcore_sys_hci); +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(rfcore_sys_hci_obj, 3, 4, rfcore_sys_hci); #endif // defined(STM32WB) diff --git a/ports/stm32/rfcore.h b/ports/stm32/rfcore.h index 39f6220a32d26..6a3c85f67dfe8 100644 --- a/ports/stm32/rfcore.h +++ b/ports/stm32/rfcore.h @@ -37,6 +37,6 @@ void rfcore_ble_set_txpower(uint8_t level); MP_DECLARE_CONST_FUN_OBJ_0(rfcore_status_obj); MP_DECLARE_CONST_FUN_OBJ_1(rfcore_fw_version_obj); -MP_DECLARE_CONST_FUN_OBJ_3(rfcore_sys_hci_obj); +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(rfcore_sys_hci_obj); #endif // MICROPY_INCLUDED_STM32_RFCORE_H From 18518e26a7a92345fdcf8ad79e4c8b3a753f2d06 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 13 Oct 2020 11:06:49 +1100 Subject: [PATCH 011/179] ports: Use correct in/out endpoint size in TUD_CDC_DESCRIPTOR. The last argument of TUD_CDC_DESCRIPTOR() is the endpoint size (or wMaxPacketSize), not the CDC RX buffer size (which can be larger than the endpoint size). Signed-off-by: Damien George --- ports/mimxrt/tusb_port.c | 3 ++- ports/nrf/drivers/usb/usb_descriptors.c | 3 ++- ports/samd/tusb_port.c | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ports/mimxrt/tusb_port.c b/ports/mimxrt/tusb_port.c index 70f8ef527cd0d..f6b09a362d18a 100644 --- a/ports/mimxrt/tusb_port.c +++ b/ports/mimxrt/tusb_port.c @@ -39,6 +39,7 @@ #define USBD_CDC_EP_OUT (0x02) #define USBD_CDC_EP_IN (0x82) #define USBD_CDC_CMD_MAX_SIZE (8) +#define USBD_CDC_IN_OUT_MAX_SIZE (512) #define USBD_STR_0 (0x00) #define USBD_STR_MANUF (0x01) @@ -70,7 +71,7 @@ static const uint8_t usbd_desc_cfg[USBD_DESC_LEN] = { TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, USBD_MAX_POWER_MA), TUD_CDC_DESCRIPTOR(USBD_ITF_CDC, USBD_STR_CDC, USBD_CDC_EP_CMD, - USBD_CDC_CMD_MAX_SIZE, USBD_CDC_EP_OUT, USBD_CDC_EP_IN, CFG_TUD_CDC_RX_BUFSIZE), + USBD_CDC_CMD_MAX_SIZE, USBD_CDC_EP_OUT, USBD_CDC_EP_IN, USBD_CDC_IN_OUT_MAX_SIZE), }; static const char *const usbd_desc_str[] = { diff --git a/ports/nrf/drivers/usb/usb_descriptors.c b/ports/nrf/drivers/usb/usb_descriptors.c index 3704e5d0dd180..f6724c1bc078c 100644 --- a/ports/nrf/drivers/usb/usb_descriptors.c +++ b/ports/nrf/drivers/usb/usb_descriptors.c @@ -39,6 +39,7 @@ #define USBD_CDC_EP_OUT (0x02) #define USBD_CDC_EP_IN (0x82) #define USBD_CDC_CMD_MAX_SIZE (8) +#define USBD_CDC_IN_OUT_MAX_SIZE (64) #define USBD_STR_0 (0x00) #define USBD_STR_MANUF (0x01) @@ -70,7 +71,7 @@ static const uint8_t usbd_desc_cfg[USBD_DESC_LEN] = { TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, USBD_MAX_POWER_MA), TUD_CDC_DESCRIPTOR(USBD_ITF_CDC, USBD_STR_CDC, USBD_CDC_EP_CMD, - USBD_CDC_CMD_MAX_SIZE, USBD_CDC_EP_OUT, USBD_CDC_EP_IN, CFG_TUD_CDC_RX_BUFSIZE), + USBD_CDC_CMD_MAX_SIZE, USBD_CDC_EP_OUT, USBD_CDC_EP_IN, USBD_CDC_IN_OUT_MAX_SIZE), }; static const char *const usbd_desc_str[] = { diff --git a/ports/samd/tusb_port.c b/ports/samd/tusb_port.c index 019e3a891c262..f3d417f1a182e 100644 --- a/ports/samd/tusb_port.c +++ b/ports/samd/tusb_port.c @@ -40,6 +40,7 @@ #define USBD_CDC_EP_OUT (0x02) #define USBD_CDC_EP_IN (0x82) #define USBD_CDC_CMD_MAX_SIZE (8) +#define USBD_CDC_IN_OUT_MAX_SIZE (64) #define USBD_STR_0 (0x00) #define USBD_STR_MANUF (0x01) @@ -71,7 +72,7 @@ static const uint8_t usbd_desc_cfg[USBD_DESC_LEN] = { TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, USBD_MAX_POWER_MA), TUD_CDC_DESCRIPTOR(USBD_ITF_CDC, USBD_STR_CDC, USBD_CDC_EP_CMD, - USBD_CDC_CMD_MAX_SIZE, USBD_CDC_EP_OUT, USBD_CDC_EP_IN, CFG_TUD_CDC_RX_BUFSIZE), + USBD_CDC_CMD_MAX_SIZE, USBD_CDC_EP_OUT, USBD_CDC_EP_IN, USBD_CDC_IN_OUT_MAX_SIZE), }; static const char *const usbd_desc_str[] = { From 56e0932485af51ec175c8f43432eee67d657b334 Mon Sep 17 00:00:00 2001 From: awachtler Date: Tue, 6 Oct 2020 22:19:05 +0200 Subject: [PATCH 012/179] tools/upip.py: Support explicit port number in host. Adding a port number other then 443 to a PyPI URL may be needed if a local server like devpi is used. --- tools/upip.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/upip.py b/tools/upip.py index 0036eac25db7b..aa8aecedfc23f 100644 --- a/tools/upip.py +++ b/tools/upip.py @@ -129,7 +129,11 @@ def url_open(url): proto, _, host, urlpath = url.split("/", 3) try: - ai = usocket.getaddrinfo(host, 443, 0, usocket.SOCK_STREAM) + port = 443 + if ":" in host: + host, port = host.split(":") + port = int(port) + ai = usocket.getaddrinfo(host, port, 0, usocket.SOCK_STREAM) except OSError as e: fatal("Unable to resolve %s (no Internet?)" % host, e) # print("Address infos:", ai) @@ -147,7 +151,7 @@ def url_open(url): warn_ussl = False # MicroPython rawsocket module supports file interface directly - s.write("GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n" % (urlpath, host)) + s.write("GET /%s HTTP/1.0\r\nHost: %s:%s\r\n\r\n" % (urlpath, host, port)) l = s.readline() protover, status, msg = l.split(None, 2) if status != b"200": From 3bc0ecbcd974716920b860904a47ee7f69afb717 Mon Sep 17 00:00:00 2001 From: Howard Lovatt Date: Fri, 9 Oct 2020 20:40:17 +1100 Subject: [PATCH 013/179] docs/library/btree.rst: Correct method typo: __detitem__ to __delitem__. --- docs/library/btree.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/library/btree.rst b/docs/library/btree.rst index de52ef7e7e44e..ba910852102d1 100644 --- a/docs/library/btree.rst +++ b/docs/library/btree.rst @@ -118,7 +118,7 @@ Methods .. method:: btree.__getitem__(key) btree.get(key, default=None, /) btree.__setitem__(key, val) - btree.__detitem__(key) + btree.__delitem__(key) btree.__contains__(key) Standard dictionary methods. From 23f9439f441173dae961de4d2fe73986c166ff8f Mon Sep 17 00:00:00 2001 From: Howard Lovatt Date: Sat, 10 Oct 2020 08:14:38 +1100 Subject: [PATCH 014/179] docs/library/machine.rst: Correct minor typo: timout to timeout. --- docs/library/machine.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/library/machine.rst b/docs/library/machine.rst index b580353d6b564..18dc6f2afaa59 100644 --- a/docs/library/machine.rst +++ b/docs/library/machine.rst @@ -79,7 +79,7 @@ Power related functions If *time_ms* is specified then this will be the maximum time in milliseconds that the sleep will last for. Otherwise the sleep can last indefinitely. - With or without a timout, execution may resume at any time if there are events + With or without a timeout, execution may resume at any time if there are events that require processing. Such events, or wake sources, should be configured before sleeping, like `Pin` change or `RTC` timeout. From cf6845b1cf4680bb2eade175aaab00428bedf8ba Mon Sep 17 00:00:00 2001 From: Howard Lovatt Date: Sat, 10 Oct 2020 08:18:24 +1100 Subject: [PATCH 015/179] docs/library/machine.Signal.rst: Correct typo: usecases to use cases. --- docs/library/machine.Signal.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/library/machine.Signal.rst b/docs/library/machine.Signal.rst index 651c8c8497fd8..1e1fcb5483a86 100644 --- a/docs/library/machine.Signal.rst +++ b/docs/library/machine.Signal.rst @@ -55,7 +55,7 @@ Following is the guide when Signal vs Pin should be used: * Use Pin: If you implement a higher-level protocol or bus to communicate with more complex devices. -The split between Pin and Signal come from the usecases above and the +The split between Pin and Signal come from the use cases above and the architecture of MicroPython: Pin offers the lowest overhead, which may be important when bit-banging protocols. But Signal adds additional flexibility on top of Pin, at the cost of minor overhead (much smaller From 4842060366a88d8f50155538ce5fc3a12c8c709a Mon Sep 17 00:00:00 2001 From: Howard Lovatt Date: Sat, 10 Oct 2020 08:31:00 +1100 Subject: [PATCH 016/179] docs/library/machine.Timer.rst: Add mention of constructor arguments. --- docs/library/machine.Timer.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/library/machine.Timer.rst b/docs/library/machine.Timer.rst index 2d1287f325780..9991d3aebc2fc 100644 --- a/docs/library/machine.Timer.rst +++ b/docs/library/machine.Timer.rst @@ -31,6 +31,8 @@ Constructors Construct a new timer object of the given id. Id of -1 constructs a virtual timer (if supported by a board). + + See ``init`` for parameters of initialisation. Methods ------- From 32c99174e143b45d056c83a33f8de7502a82370c Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Thu, 10 Sep 2020 17:46:25 +1000 Subject: [PATCH 017/179] unix/mpconfigport.h: Enable MICROPY_PY_DELATTR_SETATTR. This is a generally useful feature and because it's part of the object model it cannot be added at runtime by some loadable Python code, so enable it on the standard unix build. --- ports/unix/mpconfigport.h | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index c74d2fd84aa5f..17f4895573ed5 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -87,6 +87,7 @@ #define MICROPY_VFS_POSIX_FILE (1) #define MICROPY_PY_FUNCTION_ATTRS (1) #define MICROPY_PY_DESCRIPTORS (1) +#define MICROPY_PY_DELATTR_SETATTR (1) #define MICROPY_PY_BUILTINS_STR_UNICODE (1) #define MICROPY_PY_BUILTINS_STR_CENTER (1) #define MICROPY_PY_BUILTINS_STR_PARTITION (1) From 97108fce5730f2342903e55d533ef2c30ebdfc13 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 21 Oct 2020 08:54:46 +1100 Subject: [PATCH 018/179] esp32/mpconfigport.h: Enable MICROPY_PY_DELATTR_SETATTR. To align with unix and stm32 ports. --- ports/esp32/mpconfigport.h | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 5bf0676b238e3..b63d1f89558c0 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -63,6 +63,7 @@ // control over Python builtins #define MICROPY_PY_FUNCTION_ATTRS (1) #define MICROPY_PY_DESCRIPTORS (1) +#define MICROPY_PY_DELATTR_SETATTR (1) #define MICROPY_PY_STR_BYTES_CMP_WARN (1) #define MICROPY_PY_BUILTINS_STR_UNICODE (1) #define MICROPY_PY_BUILTINS_STR_CENTER (1) From a93d9b8c2d4f7b020a906007702e01a1485e8043 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Fri, 9 Oct 2020 22:02:46 +0200 Subject: [PATCH 019/179] stm32: Fix broken build when FAT FS multi-partition is disabled. --- ports/stm32/sdcard.c | 2 ++ ports/stm32/storage.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ports/stm32/sdcard.c b/ports/stm32/sdcard.c index 7d79e9f47b1a8..b255ee82ca9be 100644 --- a/ports/stm32/sdcard.c +++ b/ports/stm32/sdcard.c @@ -866,7 +866,9 @@ void sdcard_init_vfs(fs_user_mount_t *vfs, int part) { vfs->base.type = &mp_fat_vfs_type; vfs->blockdev.flags |= MP_BLOCKDEV_FLAG_NATIVE | MP_BLOCKDEV_FLAG_HAVE_IOCTL; vfs->fatfs.drv = vfs; + #if MICROPY_FATFS_MULTI_PARTITION vfs->fatfs.part = part; + #endif vfs->blockdev.readblocks[0] = MP_OBJ_FROM_PTR(&pyb_sdcard_readblocks_obj); vfs->blockdev.readblocks[1] = MP_OBJ_FROM_PTR(&pyb_sdcard_obj); vfs->blockdev.readblocks[2] = MP_OBJ_FROM_PTR(sdcard_read_blocks); // native version diff --git a/ports/stm32/storage.c b/ports/stm32/storage.c index 0fefcbab9063c..c8805d682986b 100644 --- a/ports/stm32/storage.c +++ b/ports/stm32/storage.c @@ -453,7 +453,9 @@ void pyb_flash_init_vfs(fs_user_mount_t *vfs) { vfs->base.type = &mp_fat_vfs_type; vfs->blockdev.flags |= MP_BLOCKDEV_FLAG_NATIVE | MP_BLOCKDEV_FLAG_HAVE_IOCTL; vfs->fatfs.drv = vfs; + #if MICROPY_FATFS_MULTI_PARTITION vfs->fatfs.part = 1; // flash filesystem lives on first partition + #endif vfs->blockdev.readblocks[0] = MP_OBJ_FROM_PTR(&pyb_flash_readblocks_obj); vfs->blockdev.readblocks[1] = MP_OBJ_FROM_PTR(&pyb_flash_obj); vfs->blockdev.readblocks[2] = MP_OBJ_FROM_PTR(storage_read_blocks); // native version From 581d43b7742628907e5a77115350e9eadd59092a Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 22 Oct 2020 11:54:38 +1100 Subject: [PATCH 020/179] stm32/usbd_cdc_interface: Check and handle CDC TX wrap-overflow. If the device is not connected over USB CDC to a host then all output to the CDC (eg initial boot messages) is written to the CDC TX buffer with wrapping, so that the most recent data is retained when the USB CDC is eventually connected (eg so the REPL banner is displayed upon connection). This commit fixes a bug in this behaviour, which was likely introduced in e4fcd216e02eef0b389c84ecd67be3114aac0a5d, where the initial data in the CDC TX buffer is repeated multiple times on first connection of the device to the host. Signed-off-by: Damien George --- ports/stm32/usbd_cdc_interface.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ports/stm32/usbd_cdc_interface.c b/ports/stm32/usbd_cdc_interface.c index 7a09128527cec..a8261a958aa09 100644 --- a/ports/stm32/usbd_cdc_interface.c +++ b/ports/stm32/usbd_cdc_interface.c @@ -196,9 +196,13 @@ static uint16_t usbd_cdc_tx_send_length(usbd_cdc_itf_t *cdc) { return MIN(usbd_cdc_tx_buffer_size(cdc), to_end); } -static void usbd_cdc_tx_buffer_put(usbd_cdc_itf_t *cdc, uint8_t data) { +static void usbd_cdc_tx_buffer_put(usbd_cdc_itf_t *cdc, uint8_t data, bool check_overflow) { cdc->tx_buf[usbd_cdc_tx_buffer_mask(cdc->tx_buf_ptr_in)] = data; cdc->tx_buf_ptr_in++; + if (check_overflow && usbd_cdc_tx_buffer_size(cdc) > USBD_CDC_TX_DATA_SIZE) { + cdc->tx_buf_ptr_out++; + cdc->tx_buf_ptr_out_next = cdc->tx_buf_ptr_out; + } } static uint8_t *usbd_cdc_tx_buffer_getp(usbd_cdc_itf_t *cdc, uint16_t len) { @@ -353,7 +357,7 @@ int usbd_cdc_tx(usbd_cdc_itf_t *cdc, const uint8_t *buf, uint32_t len, uint32_t } // Write data to device buffer - usbd_cdc_tx_buffer_put(cdc, buf[i]); + usbd_cdc_tx_buffer_put(cdc, buf[i], false); } usbd_cdc_try_tx(cdc); @@ -386,7 +390,7 @@ void usbd_cdc_tx_always(usbd_cdc_itf_t *cdc, const uint8_t *buf, uint32_t len) { } } - usbd_cdc_tx_buffer_put(cdc, buf[i]); + usbd_cdc_tx_buffer_put(cdc, buf[i], true); } usbd_cdc_try_tx(cdc); } From 6eebdbc495a52d1965d7e9255816db310041f96a Mon Sep 17 00:00:00 2001 From: Kevin Thomas Date: Sat, 3 Oct 2020 08:35:41 -0400 Subject: [PATCH 021/179] docs/reference/glossary.rst: Fix minor grammar error, An -> A. --- docs/reference/glossary.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/glossary.rst b/docs/reference/glossary.rst index d63f372298f9e..27a66aa76c075 100644 --- a/docs/reference/glossary.rst +++ b/docs/reference/glossary.rst @@ -177,7 +177,7 @@ Glossary typically accessible on a host PC via USB. stream - Also known as a "file-like object". An Python object which provides + Also known as a "file-like object". A Python object which provides sequential read-write access to the underlying data. A stream object implements a corresponding interface, which consists of methods like ``read()``, ``write()``, ``readinto()``, ``seek()``, ``flush()``, From 6324c3e05499a31c5a80ad58f030e693f459f294 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Sat, 3 Oct 2020 10:42:57 +0200 Subject: [PATCH 022/179] py/scope: Name and use id_kind_type_t. The function scope_find_or_add_id used to take a scope_kind_t enum and save it in an uint8_t. Saving an enum in a uint8_t is fine, but everywhere this function is called it is not actually given a scope_kind_t but an anonymous enum instead. Let's give this enum a name and use that as the argument type. This doesn't change the generated code, but is a C type mismatch that unfortunately doesn't show up unless you enable -Wenum-conversion. --- py/scope.c | 2 +- py/scope.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/py/scope.c b/py/scope.c index 073c2a38c2c21..98e02fb53fe31 100644 --- a/py/scope.c +++ b/py/scope.c @@ -72,7 +72,7 @@ void scope_free(scope_t *scope) { m_del(scope_t, scope, 1); } -id_info_t *scope_find_or_add_id(scope_t *scope, qstr qst, scope_kind_t kind) { +id_info_t *scope_find_or_add_id(scope_t *scope, qstr qst, id_info_kind_t kind) { id_info_t *id_info = scope_find(scope, qst); if (id_info != NULL) { return id_info; diff --git a/py/scope.h b/py/scope.h index b52d98ea1c72d..edf164c4adace 100644 --- a/py/scope.h +++ b/py/scope.h @@ -29,14 +29,14 @@ #include "py/parse.h" #include "py/emitglue.h" -enum { +typedef enum { ID_INFO_KIND_UNDECIDED, ID_INFO_KIND_GLOBAL_IMPLICIT, ID_INFO_KIND_GLOBAL_EXPLICIT, ID_INFO_KIND_LOCAL, // in a function f, written and only referenced by f ID_INFO_KIND_CELL, // in a function f, read/written by children of f ID_INFO_KIND_FREE, // in a function f, belongs to the parent of f -}; +} id_info_kind_t; enum { ID_FLAG_IS_PARAM = 0x01, @@ -92,7 +92,7 @@ typedef struct _scope_t { scope_t *scope_new(scope_kind_t kind, mp_parse_node_t pn, qstr source_file, mp_uint_t emit_options); void scope_free(scope_t *scope); -id_info_t *scope_find_or_add_id(scope_t *scope, qstr qstr, scope_kind_t kind); +id_info_t *scope_find_or_add_id(scope_t *scope, qstr qstr, id_info_kind_t kind); id_info_t *scope_find(scope_t *scope, qstr qstr); id_info_t *scope_find_global(scope_t *scope, qstr qstr); void scope_check_to_close_over(scope_t *scope, id_info_t *id); From 6d3aa16443c3eeef3945bf3d31429a655f690e0c Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Fri, 13 Dec 2019 08:24:18 +0100 Subject: [PATCH 023/179] py/objexcept: Compare mp_emergency_exception_buf_size signed. mp_emergency_exception_buf_size is signed, so let's make sure we compare it as such. --- py/objexcept.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/py/objexcept.c b/py/objexcept.c index 517427ed71365..885032c3e3eed 100644 --- a/py/objexcept.c +++ b/py/objexcept.c @@ -208,7 +208,7 @@ mp_obj_t mp_obj_exception_make_new(const mp_obj_type_t *type, size_t n_args, siz // reserved room (after the traceback data) for a tuple with 1 element. // Otherwise we are free to use the whole buffer after the traceback data. if (o_tuple == NULL && mp_emergency_exception_buf_size >= - EMG_BUF_TUPLE_OFFSET + EMG_BUF_TUPLE_SIZE(n_args)) { + (mp_int_t)(EMG_BUF_TUPLE_OFFSET + EMG_BUF_TUPLE_SIZE(n_args))) { o_tuple = (mp_obj_tuple_t *) ((uint8_t *)MP_STATE_VM(mp_emergency_exception_buf) + EMG_BUF_TUPLE_OFFSET); } @@ -383,7 +383,7 @@ mp_obj_t mp_obj_new_exception_msg(const mp_obj_type_t *exc_type, mp_rom_error_te // that buffer to store the string object, reserving room at the start for the // traceback and 1-tuple. if (o_str == NULL - && mp_emergency_exception_buf_size >= EMG_BUF_STR_OFFSET + sizeof(mp_obj_str_t)) { + && mp_emergency_exception_buf_size >= (mp_int_t)(EMG_BUF_STR_OFFSET + sizeof(mp_obj_str_t))) { o_str = (mp_obj_str_t *)((uint8_t *)MP_STATE_VM(mp_emergency_exception_buf) + EMG_BUF_STR_OFFSET); } @@ -465,7 +465,7 @@ mp_obj_t mp_obj_new_exception_msg_vlist(const mp_obj_type_t *exc_type, mp_rom_er // that buffer to store the string object and its data (at least 16 bytes for // the string data), reserving room at the start for the traceback and 1-tuple. if ((o_str == NULL || o_str_buf == NULL) - && mp_emergency_exception_buf_size >= EMG_BUF_STR_OFFSET + sizeof(mp_obj_str_t) + 16) { + && mp_emergency_exception_buf_size >= (mp_int_t)(EMG_BUF_STR_OFFSET + sizeof(mp_obj_str_t) + 16)) { used_emg_buf = true; o_str = (mp_obj_str_t *)((uint8_t *)MP_STATE_VM(mp_emergency_exception_buf) + EMG_BUF_STR_OFFSET); o_str_buf = (byte *)((uint8_t *)MP_STATE_VM(mp_emergency_exception_buf) + EMG_BUF_STR_BUF_OFFSET); @@ -573,7 +573,7 @@ void mp_obj_exception_add_traceback(mp_obj_t self_in, qstr file, size_t line, qs self->traceback_data = m_new_maybe(size_t, TRACEBACK_ENTRY_LEN); if (self->traceback_data == NULL) { #if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF - if (mp_emergency_exception_buf_size >= EMG_BUF_TRACEBACK_OFFSET + EMG_BUF_TRACEBACK_SIZE) { + if (mp_emergency_exception_buf_size >= (mp_int_t)(EMG_BUF_TRACEBACK_OFFSET + EMG_BUF_TRACEBACK_SIZE)) { // There is room in the emergency buffer for traceback data size_t *tb = (size_t *)((uint8_t *)MP_STATE_VM(mp_emergency_exception_buf) + EMG_BUF_TRACEBACK_OFFSET); From fdd6fa389ed68a5d0761f7cb71c94db5e927d741 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Sat, 3 Oct 2020 10:51:59 +0200 Subject: [PATCH 024/179] py: Use unsigned comparison of chars. On x86 chars are signed, but we're comparing a char to '0' + unsigned int, which is promoted to an unsigned int. Let's promote the char to unsigned before doing the comparison to avoid weird corner cases. --- py/emitinlinethumb.c | 2 +- py/emitinlinextensa.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/py/emitinlinethumb.c b/py/emitinlinethumb.c index 2853da26c5465..cffaa4bb89950 100644 --- a/py/emitinlinethumb.c +++ b/py/emitinlinethumb.c @@ -108,7 +108,7 @@ STATIC mp_uint_t emit_inline_thumb_count_params(emit_inline_asm_t *emit, mp_uint return 0; } const char *p = qstr_str(MP_PARSE_NODE_LEAF_ARG(pn_params[i])); - if (!(strlen(p) == 2 && p[0] == 'r' && p[1] == '0' + i)) { + if (!(strlen(p) == 2 && p[0] == 'r' && (mp_uint_t)p[1] == '0' + i)) { emit_inline_thumb_error_msg(emit, MP_ERROR_TEXT("parameters must be registers in sequence r0 to r3")); return 0; } diff --git a/py/emitinlinextensa.c b/py/emitinlinextensa.c index 6cc2e4d34b6a8..5dac2ae3907f1 100644 --- a/py/emitinlinextensa.c +++ b/py/emitinlinextensa.c @@ -92,7 +92,7 @@ STATIC mp_uint_t emit_inline_xtensa_count_params(emit_inline_asm_t *emit, mp_uin return 0; } const char *p = qstr_str(MP_PARSE_NODE_LEAF_ARG(pn_params[i])); - if (!(strlen(p) == 2 && p[0] == 'a' && p[1] == '2' + i)) { + if (!(strlen(p) == 2 && p[0] == 'a' && (mp_uint_t)p[1] == '2' + i)) { emit_inline_xtensa_error_msg(emit, MP_ERROR_TEXT("parameters must be registers in sequence a2 to a5")); return 0; } From 9aa58cf8bac353297ff5e7b4f3331e5618046095 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Sat, 3 Oct 2020 11:23:12 +0200 Subject: [PATCH 025/179] py, extmod: Add explicit initializers for default values. When compiling with -Wextra which includes -Wmissing-field-initializers GCC will warn that the defval field of mp_arg_val_t is not initialized. This is just a warning as it is defined to be zero initialized, but since it is a union it makes sense to be explicit about which member we're going to use, so add the explicit initializers and get rid of the warning. --- extmod/machine_i2c.c | 4 ++-- extmod/vfs_lfs.c | 2 +- py/modmath.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extmod/machine_i2c.c b/extmod/machine_i2c.c index 9203f16f6d785..12c9abbcbaece 100644 --- a/extmod/machine_i2c.c +++ b/extmod/machine_i2c.c @@ -311,8 +311,8 @@ STATIC void mp_machine_soft_i2c_print(const mp_print_t *print, mp_obj_t self_in, STATIC void machine_i2c_obj_init_helper(machine_i2c_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_scl, ARG_sda, ARG_freq, ARG_timeout }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_scl, MP_ARG_REQUIRED | MP_ARG_OBJ }, - { MP_QSTR_sda, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_scl, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_sda, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 400000} }, { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 255} }, }; diff --git a/extmod/vfs_lfs.c b/extmod/vfs_lfs.c index 9cf3eb11083cf..dd78269a460ab 100644 --- a/extmod/vfs_lfs.c +++ b/extmod/vfs_lfs.c @@ -35,7 +35,7 @@ enum { LFS_MAKE_ARG_bdev, LFS_MAKE_ARG_readsize, LFS_MAKE_ARG_progsize, LFS_MAKE_ARG_lookahead, LFS_MAKE_ARG_mtime }; static const mp_arg_t lfs_make_allowed_args[] = { - { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_readsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 32} }, { MP_QSTR_progsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 32} }, { MP_QSTR_lookahead, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 32} }, diff --git a/py/modmath.c b/py/modmath.c index b7948f39e7c91..3ab3ff334c41b 100644 --- a/py/modmath.c +++ b/py/modmath.c @@ -206,8 +206,8 @@ MATH_FUN_1(lgamma, lgamma) STATIC mp_obj_t mp_math_isclose(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_a, ARG_b, ARG_rel_tol, ARG_abs_tol }; static const mp_arg_t allowed_args[] = { - {MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ}, - {MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ}, + {MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, + {MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, {MP_QSTR_rel_tol, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, {MP_QSTR_abs_tol, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_SMALL_INT(0)}}, }; From f1f6ef7b17dc97f784a4cdb33154800129dc6d26 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Thu, 28 Nov 2019 12:50:08 +0100 Subject: [PATCH 026/179] py/vmentrytable: Ignore GCC -Woverride-init. Like Clang, GCC warns about this file, but only with -Woverride-init which is enabled by -Wextra. Disable the warnings for this file just like we do for Clang to make -Wextra happy. --- py/vmentrytable.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/py/vmentrytable.h b/py/vmentrytable.h index 068214bf916cf..7912270872759 100644 --- a/py/vmentrytable.h +++ b/py/vmentrytable.h @@ -30,6 +30,10 @@ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Winitializer-overrides" #endif // __clang__ +#if __GNUC__ >= 5 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Woverride-init" +#endif // __GNUC__ >= 5 static const void *const entry_table[256] = { [0 ... 255] = &&entry_default, @@ -119,3 +123,6 @@ static const void *const entry_table[256] = { #if __clang__ #pragma clang diagnostic pop #endif // __clang__ +#if __GNUC__ >= 5 +#pragma GCC diagnostic pop +#endif // __GNUC__ >= 5 From dde3db21fcd8d810bb59e0c56dfa5fd9208e1544 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Sat, 3 Oct 2020 12:19:48 +0200 Subject: [PATCH 027/179] extmod: Disable -Wmissing-field-initializers for lfs2. --- extmod/extmod.mk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extmod/extmod.mk b/extmod/extmod.mk index e312acba86db4..b000b058d7bd4 100644 --- a/extmod/extmod.mk +++ b/extmod/extmod.mk @@ -37,6 +37,8 @@ SRC_MOD += $(addprefix $(LITTLEFS_DIR)/,\ lfs2.c \ lfs2_util.c \ ) + +$(BUILD)/$(LITTLEFS_DIR)/lfs2.o: CFLAGS += -Wno-missing-field-initializers endif ################################################################################ From ccd92335a11f03597f94da2ac937811ff3fa50cf Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Thu, 28 Nov 2019 12:47:21 +0100 Subject: [PATCH 028/179] py, extmod: Introduce and use MP_FALLTHROUGH macro. Newer GCC versions are able to warn about switch cases that fall through. This is usually a sign of a forgotten break statement, but in the few cases where a fall through is intended we annotate it with this macro to avoid the warning. --- extmod/moductypes.c | 3 ++- extmod/re1.5/compilecode.c | 1 + extmod/re1.5/recursiveloop.c | 1 + py/gc.c | 3 ++- py/lexer.c | 3 ++- py/mpconfig.h | 7 +++++++ py/objset.c | 1 + 7 files changed, 16 insertions(+), 3 deletions(-) diff --git a/extmod/moductypes.c b/extmod/moductypes.c index 811258424a3bf..c5fbf12e42b9d 100644 --- a/extmod/moductypes.c +++ b/extmod/moductypes.c @@ -506,6 +506,7 @@ STATIC mp_obj_t uctypes_struct_attr_op(mp_obj_t self_in, qstr attr, mp_obj_t set return mp_obj_new_bytearray_by_ref(uctypes_struct_agg_size(sub, self->flags, &dummy), self->addr + offset); } // Fall thru to return uctypes struct object + MP_FALLTHROUGH } case PTR: { mp_obj_uctypes_struct_t *o = m_new_obj(mp_obj_uctypes_struct_t); @@ -627,7 +628,7 @@ STATIC mp_obj_t uctypes_struct_unary_op(mp_unary_op_t op, mp_obj_t self_in) { return mp_obj_new_int((mp_int_t)(uintptr_t)p); } } - /* fallthru */ + MP_FALLTHROUGH default: return MP_OBJ_NULL; // op not supported diff --git a/extmod/re1.5/compilecode.c b/extmod/re1.5/compilecode.c index 3f54b3993fd36..c4d12af87a32e 100644 --- a/extmod/re1.5/compilecode.c +++ b/extmod/re1.5/compilecode.c @@ -29,6 +29,7 @@ static const char *_compilecode(const char *re, ByteProg *prog, int sizecode) prog->len++; break; } + MP_FALLTHROUGH default: term = PC; EMIT(PC++, Char); diff --git a/extmod/re1.5/recursiveloop.c b/extmod/re1.5/recursiveloop.c index bb337decfbc95..f8cb92629200a 100644 --- a/extmod/re1.5/recursiveloop.c +++ b/extmod/re1.5/recursiveloop.c @@ -22,6 +22,7 @@ recursiveloop(char *pc, const char *sp, Subject *input, const char **subp, int n case Char: if(*sp != *pc++) return 0; + MP_FALLTHROUGH case Any: sp++; continue; diff --git a/py/gc.c b/py/gc.c index 9c6336852a0cd..767f1b81c4dfa 100644 --- a/py/gc.c +++ b/py/gc.c @@ -298,7 +298,8 @@ STATIC void gc_sweep(void) { #if MICROPY_PY_GC_COLLECT_RETVAL MP_STATE_MEM(gc_collected)++; #endif - // fall through to free the head + // fall through to free the head + MP_FALLTHROUGH case AT_TAIL: if (free_tail) { diff --git a/py/lexer.c b/py/lexer.c index 7d2a251d41d75..07ea2b96ab578 100644 --- a/py/lexer.c +++ b/py/lexer.c @@ -346,7 +346,8 @@ STATIC void parse_string_literal(mp_lexer_t *lex, bool is_raw) { vstr_add_char(&lex->vstr, '\\'); break; } - // Otherwise fall through. + // Otherwise fall through. + MP_FALLTHROUGH case 'x': { mp_uint_t num = 0; if (!get_hex(lex, (c == 'x' ? 2 : c == 'u' ? 4 : 8), &num)) { diff --git a/py/mpconfig.h b/py/mpconfig.h index cc83f3850d695..854188b66b585 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1643,6 +1643,13 @@ typedef double mp_float_t; #endif #endif +// Explicitly annotate switch case fall throughs +#if defined(__GNUC__) && __GNUC__ >= 7 +#define MP_FALLTHROUGH __attribute__((fallthrough)); +#else +#define MP_FALLTHROUGH +#endif + #ifndef MP_HTOBE16 #if MP_ENDIANNESS_LITTLE #define MP_HTOBE16(x) ((uint16_t)((((x) & 0xff) << 8) | (((x) >> 8) & 0xff))) diff --git a/py/objset.c b/py/objset.c index f31a901a7008a..dac9b1138291e 100644 --- a/py/objset.c +++ b/py/objset.c @@ -445,6 +445,7 @@ STATIC mp_obj_t set_unary_op(mp_unary_op_t op, mp_obj_t self_in) { } return MP_OBJ_NEW_SMALL_INT(hash); } + MP_FALLTHROUGH #endif default: return MP_OBJ_NULL; // op not supported From bef412789ea93c521bd9c2dddc22b9b3484da574 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Sat, 3 Oct 2020 11:29:16 +0200 Subject: [PATCH 029/179] mpy-cross: Enable more warnings. --- mpy-cross/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mpy-cross/Makefile b/mpy-cross/Makefile index f80ee761b7bcf..971f2f81aa593 100644 --- a/mpy-cross/Makefile +++ b/mpy-cross/Makefile @@ -18,7 +18,7 @@ INC += -I$(TOP) # compiler settings CWARN = -Wall -Werror -CWARN += -Wpointer-arith -Wuninitialized +CWARN += -Wextra -Wno-unused-parameter -Wpointer-arith CFLAGS = $(INC) $(CWARN) -std=gnu99 $(CFLAGS_MOD) $(COPT) $(CFLAGS_EXTRA) CFLAGS += -fdata-sections -ffunction-sections -fno-asynchronous-unwind-tables From 05f95682e7ddfb08c317e83826df9a1d636676f3 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Sat, 3 Oct 2020 11:31:13 +0200 Subject: [PATCH 030/179] unix: Enable more warnings. --- ports/unix/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/unix/Makefile b/ports/unix/Makefile index ff5f355022bc5..7380e5e41259c 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -39,7 +39,7 @@ INC += -I$(BUILD) # compiler settings CWARN = -Wall -Werror -CWARN += -Wpointer-arith -Wuninitialized -Wdouble-promotion -Wsign-compare -Wfloat-conversion +CWARN += -Wextra -Wno-unused-parameter -Wpointer-arith -Wdouble-promotion -Wfloat-conversion CFLAGS += $(INC) $(CWARN) -std=gnu99 -DUNIX $(CFLAGS_MOD) $(COPT) -I$(VARIANT_DIR) $(CFLAGS_EXTRA) # Debugging/Optimization From 368c1a09611f2a139c0e401eeb4359f9cc2a7c57 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 28 Oct 2020 00:54:30 +1100 Subject: [PATCH 031/179] tests/thread/stress_schedule.py: Assign globals before running test. When threading is enabled without the GIL then there can be races between the threads accessing the globals dict. Avoid this issue by making sure all globals variables are allocated before starting the threads. Signed-off-by: Damien George --- tests/thread/stress_schedule.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/thread/stress_schedule.py b/tests/thread/stress_schedule.py index c5a402b3a3c36..8be7f2d737874 100644 --- a/tests/thread/stress_schedule.py +++ b/tests/thread/stress_schedule.py @@ -14,7 +14,11 @@ gc.disable() +_NUM_TASKS = 10000 +_TIMEOUT_MS = 10000 + n = 0 # How many times the task successfully ran. +t = None # Start time of test, assigned here to preallocate entry in globals dict. def task(x): @@ -34,9 +38,6 @@ def thread(): for i in range(8): _thread.start_new_thread(thread, ()) -_NUM_TASKS = const(10000) -_TIMEOUT_MS = const(10000) - # Wait up to 10 seconds for 10000 tasks to be scheduled. t = utime.ticks_ms() while n < _NUM_TASKS and utime.ticks_diff(utime.ticks_ms(), t) < _TIMEOUT_MS: From 0118c07916c24a6ccb6dbd0ea904312f01798b40 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 28 Oct 2020 17:19:52 +1100 Subject: [PATCH 032/179] stm32/machine_adc: Fix ADC auto-calibration to run when ADC not enabled. Prior to this commit, the ADC calibration code was never executing because ADVREGEN bit was set making the CR register always non-zero. This commit changes the logic so that ADC calibration is always run when the ADC is disabled and an ADC channel is initialised. It also uses the LL API functions to do the calibration, to make sure it is done correctly on each MCU variant. Signed-off-by: Damien George --- ports/stm32/boards/stm32f0xx_hal_conf_base.h | 1 + ports/stm32/boards/stm32h7xx_hal_conf_base.h | 1 + ports/stm32/boards/stm32l0xx_hal_conf_base.h | 1 + ports/stm32/boards/stm32wbxx_hal_conf_base.h | 1 + ports/stm32/machine_adc.c | 14 ++++++++++---- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/ports/stm32/boards/stm32f0xx_hal_conf_base.h b/ports/stm32/boards/stm32f0xx_hal_conf_base.h index 9cb7761ac2ca4..faceda2f4fa90 100644 --- a/ports/stm32/boards/stm32f0xx_hal_conf_base.h +++ b/ports/stm32/boards/stm32f0xx_hal_conf_base.h @@ -47,6 +47,7 @@ #include "stm32f0xx_hal_uart.h" #include "stm32f0xx_hal_usart.h" #include "stm32f0xx_hal_wwdg.h" +#include "stm32f0xx_ll_adc.h" // Enable various HAL modules #define HAL_MODULE_ENABLED diff --git a/ports/stm32/boards/stm32h7xx_hal_conf_base.h b/ports/stm32/boards/stm32h7xx_hal_conf_base.h index 5c97e2c44b720..9f43da40308f7 100644 --- a/ports/stm32/boards/stm32h7xx_hal_conf_base.h +++ b/ports/stm32/boards/stm32h7xx_hal_conf_base.h @@ -53,6 +53,7 @@ #include "stm32h7xx_hal_uart.h" #include "stm32h7xx_hal_usart.h" #include "stm32h7xx_hal_wwdg.h" +#include "stm32h7xx_ll_adc.h" // Enable various HAL modules #define HAL_ADC_MODULE_ENABLED diff --git a/ports/stm32/boards/stm32l0xx_hal_conf_base.h b/ports/stm32/boards/stm32l0xx_hal_conf_base.h index ed524fecca9c3..b100daaa98eb3 100644 --- a/ports/stm32/boards/stm32l0xx_hal_conf_base.h +++ b/ports/stm32/boards/stm32l0xx_hal_conf_base.h @@ -46,6 +46,7 @@ #include "stm32l0xx_hal_uart.h" #include "stm32l0xx_hal_usart.h" #include "stm32l0xx_hal_wwdg.h" +#include "stm32l0xx_ll_adc.h" // Enable various HAL modules #define HAL_MODULE_ENABLED diff --git a/ports/stm32/boards/stm32wbxx_hal_conf_base.h b/ports/stm32/boards/stm32wbxx_hal_conf_base.h index 8dbc9ecea7f12..83d07ad5b1c3b 100644 --- a/ports/stm32/boards/stm32wbxx_hal_conf_base.h +++ b/ports/stm32/boards/stm32wbxx_hal_conf_base.h @@ -41,6 +41,7 @@ #include "stm32wbxx_hal_tim.h" #include "stm32wbxx_hal_uart.h" #include "stm32wbxx_hal_usart.h" +#include "stm32wbxx_ll_adc.h" // Enable various HAL modules #define HAL_MODULE_ENABLED diff --git a/ports/stm32/machine_adc.c b/ports/stm32/machine_adc.c index f29896d37cf26..9c20f0f954989 100644 --- a/ports/stm32/machine_adc.c +++ b/ports/stm32/machine_adc.c @@ -156,10 +156,16 @@ STATIC void adc_config(ADC_TypeDef *adc, uint32_t bits) { #endif #if ADC_V2 - if (adc->CR == 0) { - // ADC hasn't been enabled so calibrate it - adc->CR |= ADC_CR_ADCAL; - while (adc->CR & ADC_CR_ADCAL) { + if (!(adc->CR & ADC_CR_ADEN)) { + // ADC isn't enabled so calibrate it now + #if defined(STM32F0) || defined(STM32L0) + LL_ADC_StartCalibration(adc); + #elif defined(STM32L4) || defined(STM32WB) + LL_ADC_StartCalibration(adc, LL_ADC_SINGLE_ENDED); + #else + LL_ADC_StartCalibration(adc, LL_ADC_CALIB_OFFSET_LINEARITY, LL_ADC_SINGLE_ENDED); + #endif + while (LL_ADC_IsCalibrationOnGoing(adc)) { } } From 03a1f94ea16a532bd4219092edb06e251d9a0ca5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 29 Oct 2020 11:31:53 +1100 Subject: [PATCH 033/179] extmod/vfs_lfs: Support mounting LFS filesystems in read-only mode. Signed-off-by: Damien George --- extmod/vfs_lfsx.c | 12 +++++++++--- tests/extmod/vfs_lfs_mount.py | 17 +++++++++++++++++ tests/extmod/vfs_lfs_mount.py.exp | 6 ++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/extmod/vfs_lfsx.c b/extmod/vfs_lfsx.c index 35d5f03c59a2c..f865e46060234 100644 --- a/extmod/vfs_lfsx.c +++ b/extmod/vfs_lfsx.c @@ -423,10 +423,16 @@ STATIC mp_obj_t MP_VFS_LFSx(statvfs)(mp_obj_t self_in, mp_obj_t path_in) { STATIC MP_DEFINE_CONST_FUN_OBJ_2(MP_VFS_LFSx(statvfs_obj), MP_VFS_LFSx(statvfs)); STATIC mp_obj_t MP_VFS_LFSx(mount)(mp_obj_t self_in, mp_obj_t readonly, mp_obj_t mkfs) { - (void)self_in; - (void)readonly; + MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in); (void)mkfs; - // already called LFSx_API(mount) in MP_VFS_LFSx(make_new) + + // Make block device read-only if requested. + if (mp_obj_is_true(readonly)) { + self->blockdev.writeblocks[0] = MP_OBJ_NULL; + } + + // Already called LFSx_API(mount) in MP_VFS_LFSx(make_new) so the filesystem is ready. + return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_3(MP_VFS_LFSx(mount_obj), MP_VFS_LFSx(mount)); diff --git a/tests/extmod/vfs_lfs_mount.py b/tests/extmod/vfs_lfs_mount.py index 2c40b2989796f..3d8cec6075c86 100644 --- a/tests/extmod/vfs_lfs_mount.py +++ b/tests/extmod/vfs_lfs_mount.py @@ -67,6 +67,23 @@ def test(bdev, vfs_class): # umount uos.umount("/lfs") + # mount read-only + vfs = vfs_class(bdev) + uos.mount(vfs, "/lfs", readonly=True) + + # test reading works + with open("/lfs/subdir/lfsmod2.py") as f: + print("lfsmod2.py:", f.read()) + + # test writing fails + try: + open("/lfs/test_write", "w") + except OSError as er: + print(repr(er)) + + # umount + uos.umount("/lfs") + # clear imported modules usys.modules.clear() diff --git a/tests/extmod/vfs_lfs_mount.py.exp b/tests/extmod/vfs_lfs_mount.py.exp index b5c52153141a2..aa654ebe05f12 100644 --- a/tests/extmod/vfs_lfs_mount.py.exp +++ b/tests/extmod/vfs_lfs_mount.py.exp @@ -2,7 +2,13 @@ test hello from lfs package hello from lfs +lfsmod2.py: print("hello from lfs") + +OSError(30,) test hello from lfs package hello from lfs +lfsmod2.py: print("hello from lfs") + +OSError(36,) From b4062894df8e17e11179dcb5f5b28b27eed33aff Mon Sep 17 00:00:00 2001 From: robert Date: Sat, 22 Aug 2020 20:56:26 +0200 Subject: [PATCH 034/179] esp32/mpconfigport.h: Seed the urandom module on import. For seeding, the RNG function of the ESP-IDF is used, which is told to be a true RNG, at least when WiFi or Bluetooth is enabled. Seeding on import is as per CPython. To obtain a reproducible sequence of pseudo-random numbers one must explicitly seed with a known value. --- ports/esp32/mpconfigport.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index b63d1f89558c0..f170d70708b79 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -6,6 +6,7 @@ #include #include +#include "esp_system.h" #if !MICROPY_ESP_IDF_4 #include "rom/ets_sys.h" @@ -141,6 +142,7 @@ #define MICROPY_PY_UBINASCII_CRC32 (1) #define MICROPY_PY_URANDOM (1) #define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) +#define MICROPY_PY_URANDOM_SEED_INIT_FUNC (esp_random()) #define MICROPY_PY_OS_DUPTERM (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_PIN_MAKE_NEW mp_pin_make_new From 057193e855ca0b5873191c327184052765781ddf Mon Sep 17 00:00:00 2001 From: robert Date: Sun, 23 Aug 2020 12:12:11 +0200 Subject: [PATCH 035/179] esp8266/mpconfigport.h: Seed the urandom module on import. For seeding, the hardware RNG of the esp8266 is used. --- ports/esp8266/mpconfigport.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/esp8266/mpconfigport.h b/ports/esp8266/mpconfigport.h index 974310f84da6e..99cf2ade4dec3 100644 --- a/ports/esp8266/mpconfigport.h +++ b/ports/esp8266/mpconfigport.h @@ -26,6 +26,7 @@ #define MICROPY_HELPER_REPL (1) #define MICROPY_HELPER_LEXER_UNIX (0) #define MICROPY_ENABLE_SOURCE_LINE (1) +#define MICROPY_MODULE_BUILTIN_INIT (1) #define MICROPY_MODULE_WEAK_LINKS (1) #define MICROPY_CAN_OVERRIDE_BUILTINS (1) #define MICROPY_USE_INTERNAL_ERRNO (1) @@ -69,6 +70,7 @@ #define MICROPY_PY_UTIMEQ (1) #define MICROPY_PY_UJSON (1) #define MICROPY_PY_URANDOM (1) +#define MICROPY_PY_URANDOM_SEED_INIT_FUNC (*WDEV_HWRNG) #define MICROPY_PY_URE (1) #define MICROPY_PY_USELECT (1) #define MICROPY_PY_UTIME_MP_HAL (1) @@ -197,4 +199,6 @@ extern const struct _mp_obj_module_t mp_module_onewire; #define MICROPY_WRAP_MP_KEYBOARD_INTERRUPT(f) MP_FASTCODE(f) #define MICROPY_WRAP_MP_SCHED_SCHEDULE(f) MP_FASTCODE(f) +#define WDEV_HWRNG ((volatile uint32_t *)0x3ff20e44) + #define _assert(expr) ((expr) ? (void)0 : __assert_func(__FILE__, __LINE__, __func__, #expr)) From 59019d7f759c78dedd8d353d24c8d64a7a9981c7 Mon Sep 17 00:00:00 2001 From: robert Date: Sun, 23 Aug 2020 12:14:23 +0200 Subject: [PATCH 036/179] stm32/mpconfigport.h: Seed the urandom module on import. For seeding the rng_get function is used, which is also the heart of uos.urandom and pyb.rng, and is a hardware RNG where available. --- ports/stm32/mpconfigport.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 7ea41bb6f4655..5f8e7ec2de1c1 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -79,6 +79,7 @@ #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) #endif #define MICROPY_STREAMS_NON_BLOCK (1) +#define MICROPY_MODULE_BUILTIN_INIT (1) #define MICROPY_MODULE_WEAK_LINKS (1) #define MICROPY_CAN_OVERRIDE_BUILTINS (1) #define MICROPY_USE_INTERNAL_ERRNO (1) @@ -167,6 +168,7 @@ #endif #ifndef MICROPY_PY_URANDOM #define MICROPY_PY_URANDOM (1) +#define MICROPY_PY_URANDOM_SEED_INIT_FUNC (rng_get()) #endif #ifndef MICROPY_PY_URANDOM_EXTRA_FUNCS #define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) @@ -427,3 +429,6 @@ static inline mp_uint_t disable_irq(void) { // We need to provide a declaration/definition of alloca() #include + +// Needed for MICROPY_PY_URANDOM_SEED_INIT_FUNC. +uint32_t rng_get(void); From 3e455e9792b5851a21ed2d94d518b21557d2a361 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 20 Oct 2020 14:17:46 +1100 Subject: [PATCH 037/179] stm32/rng: Use SysTick+RTC+unique-id to seed pRNG for MCUs without RNG. The same seed will only occur if the board is the same, the RTC has the same time (eg freshly powered up) and the first call to this function (eg via an "import random") is done at exactly the same time since reset. Signed-off-by: Damien George --- ports/stm32/rng.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/ports/stm32/rng.c b/ports/stm32/rng.c index b23941998a475..eea02f72657de 100644 --- a/ports/stm32/rng.c +++ b/ports/stm32/rng.c @@ -24,6 +24,7 @@ * THE SOFTWARE. */ +#include "rtc.h" #include "rng.h" #if MICROPY_HW_ENABLE_RNG @@ -63,16 +64,26 @@ MP_DEFINE_CONST_FUN_OBJ_0(pyb_rng_get_obj, pyb_rng_get); #else // MICROPY_HW_ENABLE_RNG // For MCUs that don't have an RNG we still need to provide a rng_get() function, -// eg for lwIP. A pseudo-RNG is not really ideal but we go with it for now. We +// eg for lwIP and random.seed(). A pseudo-RNG is not really ideal but we go with +// it for now, seeding with numbers which will be somewhat different each time. We // don't want to use urandom's pRNG because then the user won't see a reproducible // random stream. // Yasmarang random number generator by Ilya Levin // http://www.literatecode.com/yasmarang STATIC uint32_t pyb_rng_yasmarang(void) { - static uint32_t pad = 0xeda4baba, n = 69, d = 233; + static bool seeded = false; + static uint32_t pad = 0, n = 0, d = 0; static uint8_t dat = 0; + if (!seeded) { + seeded = true; + rtc_init_finalise(); + pad = *(uint32_t *)MP_HAL_UNIQUE_ID_ADDRESS ^ SysTick->VAL; + n = RTC->TR; + d = RTC->SSR; + } + pad += dat + d * n; pad = (pad << 3) + (pad >> 29); n = pad | 2; From 6f34800884f0a9a0c7116a7c5b94c6db38c4b417 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 20 Oct 2020 14:20:00 +1100 Subject: [PATCH 038/179] extmod/modurandom: Support urandom.seed() without an argument. If a port provides MICROPY_PY_URANDOM_SEED_INIT_FUNC as a source of randomness then this will be used when urandom.seed() is called without an argument (or with None as the argument) to seed the pRNG. Other related changes in this commit: - mod_urandom___init__ is changed to call seed() without arguments, instead of explicitly passing in the result of MICROPY_PY_URANDOM_SEED_INIT_FUNC. - mod_urandom___init__ will only ever seed the pRNG once (before it could seed it again if imported by, eg, random and then urandom). - The Yasmarang state is moved to the BSS for builds where the state is guaranteed to be initialised on import of the (u)random module. Signed-off-by: Damien George --- extmod/modurandom.c | 41 ++++++++++++++++++++++++---- tests/extmod/urandom_seed_default.py | 30 ++++++++++++++++++++ 2 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 tests/extmod/urandom_seed_default.py diff --git a/extmod/modurandom.c b/extmod/modurandom.c index ab83b0f701234..5a736c1ebc628 100644 --- a/extmod/modurandom.c +++ b/extmod/modurandom.c @@ -31,15 +31,29 @@ #if MICROPY_PY_URANDOM +// Work out if the seed will be set on import or not. +#if MICROPY_MODULE_BUILTIN_INIT && defined(MICROPY_PY_URANDOM_SEED_INIT_FUNC) +#define SEED_ON_IMPORT (1) +#else +#define SEED_ON_IMPORT (0) +#endif + // Yasmarang random number generator // by Ilya Levin // http://www.literatecode.com/yasmarang // Public Domain #if !MICROPY_ENABLE_DYNRUNTIME +#if SEED_ON_IMPORT +// If the state is seeded on import then keep these variables in the BSS. +STATIC uint32_t yasmarang_pad, yasmarang_n, yasmarang_d; +STATIC uint8_t yasmarang_dat; +#else +// Without seed-on-import these variables must be initialised via the data section. STATIC uint32_t yasmarang_pad = 0xeda4baba, yasmarang_n = 69, yasmarang_d = 233; STATIC uint8_t yasmarang_dat = 0; #endif +#endif STATIC uint32_t yasmarang(void) { yasmarang_pad += yasmarang_dat + yasmarang_d * yasmarang_n; @@ -83,15 +97,24 @@ STATIC mp_obj_t mod_urandom_getrandbits(mp_obj_t num_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_urandom_getrandbits_obj, mod_urandom_getrandbits); -STATIC mp_obj_t mod_urandom_seed(mp_obj_t seed_in) { - mp_uint_t seed = mp_obj_get_int_truncated(seed_in); +STATIC mp_obj_t mod_urandom_seed(size_t n_args, const mp_obj_t *args) { + mp_uint_t seed; + if (n_args == 0 || args[0] == mp_const_none) { + #ifdef MICROPY_PY_URANDOM_SEED_INIT_FUNC + seed = MICROPY_PY_URANDOM_SEED_INIT_FUNC; + #else + mp_raise_ValueError(MP_ERROR_TEXT("no default seed")); + #endif + } else { + seed = mp_obj_get_int_truncated(args[0]); + } yasmarang_pad = seed; yasmarang_n = 69; yasmarang_d = 233; yasmarang_dat = 0; return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_urandom_seed_obj, mod_urandom_seed); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_urandom_seed_obj, 0, 1, mod_urandom_seed); #if MICROPY_PY_URANDOM_EXTRA_FUNCS @@ -189,9 +212,15 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_urandom_uniform_obj, mod_urandom_uniform); #endif // MICROPY_PY_URANDOM_EXTRA_FUNCS -#ifdef MICROPY_PY_URANDOM_SEED_INIT_FUNC +#if SEED_ON_IMPORT STATIC mp_obj_t mod_urandom___init__() { - mod_urandom_seed(MP_OBJ_NEW_SMALL_INT(MICROPY_PY_URANDOM_SEED_INIT_FUNC)); + // This module may be imported by more than one name so need to ensure + // that it's only ever seeded once. + static bool seeded = false; + if (!seeded) { + seeded = true; + mod_urandom_seed(0, NULL); + } return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_urandom___init___obj, mod_urandom___init__); @@ -200,7 +229,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_urandom___init___obj, mod_urandom___init__) #if !MICROPY_ENABLE_DYNRUNTIME STATIC const mp_rom_map_elem_t mp_module_urandom_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_urandom) }, - #ifdef MICROPY_PY_URANDOM_SEED_INIT_FUNC + #if SEED_ON_IMPORT { MP_ROM_QSTR(MP_QSTR___init__), MP_ROM_PTR(&mod_urandom___init___obj) }, #endif { MP_ROM_QSTR(MP_QSTR_getrandbits), MP_ROM_PTR(&mod_urandom_getrandbits_obj) }, diff --git a/tests/extmod/urandom_seed_default.py b/tests/extmod/urandom_seed_default.py new file mode 100644 index 0000000000000..a032b9362b701 --- /dev/null +++ b/tests/extmod/urandom_seed_default.py @@ -0,0 +1,30 @@ +# test urandom.seed() without any arguments + +try: + import urandom as random +except ImportError: + try: + import random + except ImportError: + print("SKIP") + raise SystemExit + +try: + random.seed() +except ValueError: + # no default seed on this platform + print("SKIP") + raise SystemExit + + +def rng_seq(): + return [random.getrandbits(16) for _ in range(10)] + + +# seed with default and check that doesn't produce the same RNG sequence +random.seed() +seq = rng_seq() +random.seed() +print(seq == rng_seq()) +random.seed(None) +print(seq == rng_seq()) From 1b723937e3fe4012ac878ded833ff6a39455e0b8 Mon Sep 17 00:00:00 2001 From: stijn Date: Wed, 26 Aug 2020 11:23:10 +0200 Subject: [PATCH 039/179] py/makeqstrdefs.py: Fix beaviour when scanning non-C preprocessed files. When process_file() is passed a preprocessed C++ file for instance it won't find any lines containing .c files and the last_fname variable remains None, so handle that gracefully. --- py/makeqstrdefs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/py/makeqstrdefs.py b/py/makeqstrdefs.py index 9449a46ee924a..47afe0adb9775 100644 --- a/py/makeqstrdefs.py +++ b/py/makeqstrdefs.py @@ -58,7 +58,8 @@ def process_file(f): elif args.mode == _MODE_COMPRESS: output.append(match) - write_out(last_fname, output) + if last_fname: + write_out(last_fname, output) return "" From 2b9f0586e7a37a673aa2c8ef298751c8d30b2667 Mon Sep 17 00:00:00 2001 From: stijn Date: Thu, 22 Oct 2020 14:09:33 +0200 Subject: [PATCH 040/179] py/makeqstrdefs.py: Process C++ files as well. Preprocessed C++ code isn't different from C code when it comes to QSTR instances so process it as well. --- py/makeqstrdefs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/makeqstrdefs.py b/py/makeqstrdefs.py index 47afe0adb9775..1db00d963356f 100644 --- a/py/makeqstrdefs.py +++ b/py/makeqstrdefs.py @@ -44,7 +44,7 @@ def process_file(f): m = re_line.match(line) assert m is not None fname = m.group(1) - if not fname.endswith(".c"): + if os.path.splitext(fname)[1] not in [".c", ".cpp"]: continue if fname != last_fname: write_out(last_fname, output) From f1666419a8aaee846f7175ccdb8799ab9deea376 Mon Sep 17 00:00:00 2001 From: stijn Date: Thu, 8 Oct 2020 16:19:26 +0200 Subject: [PATCH 041/179] py/mkrules.mk: Add target for compiling C++ files. Move the target from the ESP32 Makefile since that does what is needed already, but also include files from user C modules as is done for the C files. --- ports/esp32/Makefile | 16 ---------------- py/mkrules.mk | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/ports/esp32/Makefile b/ports/esp32/Makefile index be282577229c4..94374eb1c4f98 100644 --- a/ports/esp32/Makefile +++ b/ports/esp32/Makefile @@ -781,22 +781,6 @@ $(BUILD)/application.elf: $(OBJ) $(LIB) $(BUILD)/esp32_out.ld $(BUILD)/esp32.pro $(Q)$(LD) $(LDFLAGS) -o $@ $(APP_LD_ARGS) $(Q)$(SIZE) $@ -define compile_cxx -$(ECHO) "CXX $<" -$(Q)$(CXX) $(CXXFLAGS) -c -MD -o $@ $< -@# The following fixes the dependency file. -@# See http://make.paulandlesley.org/autodep.html for details. -@# Regex adjusted from the above to play better with Windows paths, etc. -@$(CP) $(@:.o=.d) $(@:.o=.P); \ - $(SED) -e 's/#.*//' -e 's/^.*: *//' -e 's/ *\\$$//' \ - -e '/^$$/ d' -e 's/$$/ :/' < $(@:.o=.d) >> $(@:.o=.P); \ - $(RM) -f $(@:.o=.d) -endef - -vpath %.cpp . $(TOP) -$(BUILD)/%.o: %.cpp - $(call compile_cxx) - ################################################################################ # Declarations to build the bootloader diff --git a/py/mkrules.mk b/py/mkrules.mk index c37c25db4bd2f..f003174ea5449 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -58,12 +58,28 @@ $(Q)$(CC) $(CFLAGS) -c -MD -o $@ $< $(RM) -f $(@:.o=.d) endef +define compile_cxx +$(ECHO) "CXX $<" +$(Q)$(CXX) $(CXXFLAGS) -c -MD -o $@ $< +@# The following fixes the dependency file. +@# See http://make.paulandlesley.org/autodep.html for details. +@# Regex adjusted from the above to play better with Windows paths, etc. +@$(CP) $(@:.o=.d) $(@:.o=.P); \ + $(SED) -e 's/#.*//' -e 's/^.*: *//' -e 's/ *\\$$//' \ + -e '/^$$/ d' -e 's/$$/ :/' < $(@:.o=.d) >> $(@:.o=.P); \ + $(RM) -f $(@:.o=.d) +endef + vpath %.c . $(TOP) $(USER_C_MODULES) $(BUILD)/%.o: %.c $(call compile_c) vpath %.c . $(TOP) $(USER_C_MODULES) +vpath %.cpp . $(TOP) $(USER_C_MODULES) +$(BUILD)/%.o: %.cpp + $(call compile_cxx) + $(BUILD)/%.pp: %.c $(ECHO) "PreProcess $<" $(Q)$(CPP) $(CFLAGS) -Wp,-C,-dD,-dI -o $@ $< From 8e94fa0d2eb94483f387b1ae2e081d1998575a7f Mon Sep 17 00:00:00 2001 From: stijn Date: Thu, 8 Oct 2020 16:40:17 +0200 Subject: [PATCH 042/179] py/makeqstrdefs.py: Support preprocessing C++ files for QSTR generation. When SCR_QSTR contains C++ files they should be preprocessed with the same compiler flags (CXXFLAGS) as they will be compiled with, to make sure code scanned for QSTR occurrences is effectively the code used in the rest of the build. The 'split SCR_QSTR in .c and .cpp files and process each with different flags' logic isn't trivial to express in a Makefile and the existing principle for deciding which files to preprocess was already rather complicated, so the actual preprocessing is moved into makeqstrdefs.py completely. --- py/makeqstrdefs.py | 59 +++++++++++++++++++++++++++++++++++++++++++++- py/mkrules.mk | 10 ++++---- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/py/makeqstrdefs.py b/py/makeqstrdefs.py index 1db00d963356f..f514ae0c1052f 100644 --- a/py/makeqstrdefs.py +++ b/py/makeqstrdefs.py @@ -8,6 +8,7 @@ from __future__ import print_function import re +import subprocess import sys import io import os @@ -20,6 +21,31 @@ _MODE_COMPRESS = "compress" +def preprocess(): + if any(src in args.dependencies for src in args.changed_sources): + sources = args.sources + elif any(args.changed_sources): + sources = args.changed_sources + else: + sources = args.sources + csources = [] + cxxsources = [] + for source in sources: + if source.endswith(".cpp"): + cxxsources.append(source) + else: + csources.append(source) + try: + os.makedirs(os.path.dirname(args.output[0])) + except OSError: + pass + with open(args.output[0], "w") as out_file: + if csources: + subprocess.check_call(args.pp + args.cflags + csources, stdout=out_file) + if cxxsources: + subprocess.check_call(args.pp + args.cxxflags + cxxsources, stdout=out_file) + + def write_out(fname, output): if output: for m, r in [("/", "__"), ("\\", "__"), (":", "@"), ("..", "@@")]: @@ -105,7 +131,7 @@ def cat_together(): if __name__ == "__main__": - if len(sys.argv) != 6: + if len(sys.argv) < 6: print("usage: %s command mode input_filename output_dir output_file" % sys.argv[0]) sys.exit(2) @@ -114,6 +140,37 @@ class Args: args = Args() args.command = sys.argv[1] + + if args.command == "pp": + named_args = { + s: [] + for s in [ + "pp", + "output", + "cflags", + "cxxflags", + "sources", + "changed_sources", + "dependencies", + ] + } + + for arg in sys.argv[1:]: + if arg in named_args: + current_tok = arg + else: + named_args[current_tok].append(arg) + + if not named_args["pp"] or len(named_args["output"]) != 1: + print("usage: %s %s ..." % (sys.argv[0], " ... ".join(named_args))) + sys.exit(2) + + for k, v in named_args.items(): + setattr(args, k, v) + + preprocess() + sys.exit(0) + args.mode = sys.argv[2] args.input_filename = sys.argv[3] # Unused for command=cat args.output_dir = sys.argv[4] diff --git a/py/mkrules.mk b/py/mkrules.mk index f003174ea5449..3bfe64d7536a1 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -15,10 +15,12 @@ CFLAGS += -DMICROPY_ROM_TEXT_COMPRESSION=1 endif # QSTR generation uses the same CFLAGS, with these modifications. +QSTR_GEN_FLAGS = -DNO_QSTR -I$(BUILD)/tmp # Note: := to force evalulation immediately. QSTR_GEN_CFLAGS := $(CFLAGS) -QSTR_GEN_CFLAGS += -DNO_QSTR -QSTR_GEN_CFLAGS += -I$(BUILD)/tmp +QSTR_GEN_CFLAGS += $(QSTR_GEN_FLAGS) +QSTR_GEN_CXXFLAGS := $(CXXFLAGS) +QSTR_GEN_CXXFLAGS += $(QSTR_GEN_FLAGS) # This file expects that OBJ contains a list of all of the object files. # The directory portion of each object file is used to locate the source @@ -95,14 +97,14 @@ $(BUILD)/%.pp: %.c # to get built before we try to compile any of them. $(OBJ): | $(HEADER_BUILD)/qstrdefs.generated.h $(HEADER_BUILD)/mpversion.h $(OBJ_EXTRA_ORDER_DEPS) -# The logic for qstr regeneration is: +# The logic for qstr regeneration (applied by makeqstrdefs.py) is: # - if anything in QSTR_GLOBAL_DEPENDENCIES is newer, then process all source files ($^) # - else, if list of newer prerequisites ($?) is not empty, then process just these ($?) # - else, process all source files ($^) [this covers "make -B" which can set $? to empty] # See more information about this process in docs/develop/qstr.rst. $(HEADER_BUILD)/qstr.i.last: $(SRC_QSTR) $(QSTR_GLOBAL_DEPENDENCIES) | $(QSTR_GLOBAL_REQUIREMENTS) $(ECHO) "GEN $@" - $(Q)$(CPP) $(QSTR_GEN_CFLAGS) $(if $(filter $?,$(QSTR_GLOBAL_DEPENDENCIES)),$^,$(if $?,$?,$^)) >$(HEADER_BUILD)/qstr.i.last + $(Q)$(PYTHON) $(PY_SRC)/makeqstrdefs.py pp $(CPP) output $(HEADER_BUILD)/qstr.i.last cflags $(QSTR_GEN_CFLAGS) cxxflags $(QSTR_GEN_CXXFLAGS) sources $^ dependencies $(QSTR_GLOBAL_DEPENDENCIES) changed_sources $? $(HEADER_BUILD)/qstr.split: $(HEADER_BUILD)/qstr.i.last $(ECHO) "GEN $@" From e498a8bd13a0ecdf1cc50f57eb6d3630e8c0c078 Mon Sep 17 00:00:00 2001 From: stijn Date: Wed, 14 Oct 2020 15:42:18 +0200 Subject: [PATCH 043/179] py: Workaround clang error when building misc.h with C++ compiler. --- py/misc.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/py/misc.h b/py/misc.h index 2dd20365c934c..aac90724460cf 100644 --- a/py/misc.h +++ b/py/misc.h @@ -272,7 +272,12 @@ typedef union _mp_float_union_t { // Map MP_COMPRESSED_ROM_TEXT to the compressed strings. // Force usage of the MP_ERROR_TEXT macro by requiring an opaque type. -typedef struct {} *mp_rom_error_text_t; +typedef struct { + #ifdef __clang__ + // Fix "error: empty struct has size 0 in C, size 1 in C++". + char dummy; + #endif +} *mp_rom_error_text_t; #include From 78c8b55067b2a3da8a2237fe8acd351d188902cb Mon Sep 17 00:00:00 2001 From: stijn Date: Thu, 8 Oct 2020 16:39:33 +0200 Subject: [PATCH 044/179] docs: Fix reference to QSTR_GEN_CFLAGS Makefile flag. --- docs/develop/qstr.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/qstr.rst b/docs/develop/qstr.rst index 1b3b9f903bc91..3550a8bd425f2 100644 --- a/docs/develop/qstr.rst +++ b/docs/develop/qstr.rst @@ -51,7 +51,7 @@ Processing happens in the following stages: through the C pre-processor. This means that any conditionally disabled code will be removed, and macros expanded. This means we don't add strings to the pool that won't be used in the final firmware. Because at this stage (thanks - to the ``NO_QSTR`` macro added by ``QSTR_GEN_EXTRA_CFLAGS``) there is no + to the ``NO_QSTR`` macro added by ``QSTR_GEN_CFLAGS``) there is no definition for ``MP_QSTR_Foo`` it passes through this stage unaffected. This file also includes comments from the preprocessor that include line number information. Note that this step only uses files that have changed, which From 0153148fd26308e4ce921a4287ac4a26af15a9fe Mon Sep 17 00:00:00 2001 From: stijn Date: Thu, 8 Oct 2020 16:44:55 +0200 Subject: [PATCH 045/179] py/py.mk: Support C++ code for user C modules. Support C++ code in .cpp files by providing CXX counterparts of the _USERMOD_ flags we have for C already. This merely enables the Makefile of user C modules to use variables specific to C++ compilation, it is still up to each port's main Makefile to also include these in the build. --- docs/develop/cmodules.rst | 9 +++++---- py/py.mk | 4 ++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/develop/cmodules.rst b/docs/develop/cmodules.rst index e616adad03353..849d0e60a2ea2 100644 --- a/docs/develop/cmodules.rst +++ b/docs/develop/cmodules.rst @@ -8,7 +8,8 @@ limitations with the Python environment, often due to an inability to access certain hardware resources or Python speed limitations. If your limitations can't be resolved with suggestions in :ref:`speed_python`, -writing some or all of your module in C is a viable option. +writing some or all of your module in C (and/or C++ if implemented for your port) +is a viable option. If your module is designed to access or work with commonly available hardware or libraries please consider implementing it inside the MicroPython @@ -29,7 +30,7 @@ Structure of an external C module A MicroPython user C module is a directory with the following files: -* ``*.c`` and/or ``*.h`` source code files for your module. +* ``*.c`` / ``*.cpp`` / ``*.h`` source code files for your module. These will typically include the low level functionality being implemented and the MicroPython binding functions to expose the functions and module(s). @@ -44,12 +45,12 @@ A MicroPython user C module is a directory with the following files: in your ``micropython.mk`` to a local make variable, eg ``EXAMPLE_MOD_DIR := $(USERMOD_DIR)`` - Your ``micropython.mk`` must add your modules C files relative to your + Your ``micropython.mk`` must add your modules source files relative to your expanded copy of ``$(USERMOD_DIR)`` to ``SRC_USERMOD``, eg ``SRC_USERMOD += $(EXAMPLE_MOD_DIR)/example.c`` If you have custom ``CFLAGS`` settings or include folders to define, these - should be added to ``CFLAGS_USERMOD``. + should be added to ``CFLAGS_USERMOD``, or ``CXXFLAGS_USERMOD``. See below for full usage example. diff --git a/py/py.mk b/py/py.mk index d864a7ed3d843..bac38f07412f9 100644 --- a/py/py.mk +++ b/py/py.mk @@ -33,7 +33,9 @@ ifneq ($(USER_C_MODULES),) # pre-define USERMOD variables as expanded so that variables are immediate # expanded as they're added to them SRC_USERMOD := +SRC_USERMOD_CXX := CFLAGS_USERMOD := +CXXFLAGS_USERMOD := LDFLAGS_USERMOD := $(foreach module, $(wildcard $(USER_C_MODULES)/*/micropython.mk), \ $(eval USERMOD_DIR = $(patsubst %/,%,$(dir $(module))))\ @@ -42,7 +44,9 @@ $(foreach module, $(wildcard $(USER_C_MODULES)/*/micropython.mk), \ ) SRC_MOD += $(patsubst $(USER_C_MODULES)/%.c,%.c,$(SRC_USERMOD)) +SRC_MOD_CXX += $(patsubst $(USER_C_MODULES)/%.cpp,%.cpp,$(SRC_USERMOD_CXX)) CFLAGS_MOD += $(CFLAGS_USERMOD) +CXXFLAGS_MOD += $(CXXFLAGS_USERMOD) LDFLAGS_MOD += $(LDFLAGS_USERMOD) endif From fad4079778f46bc21dd19a674b31b4c3c7eb6a91 Mon Sep 17 00:00:00 2001 From: stijn Date: Thu, 8 Oct 2020 16:52:25 +0200 Subject: [PATCH 046/179] esp32,unix: Support building C++ code. Support building .cpp files and linking them into the micropython executable in a way similar to how it is done for .c files. The main incentive here is to enable user C modules to use C++ files (which are put in SRC_MOD_CXX by py.mk) since the core itself does not utilize C++. However, to verify build functionality a unix overage test is added. The esp32 port already has CXXFLAGS so just add the user modules' flags to it. For the unix port use a copy of the CFLAGS but strip the ones which are not usable for C++. --- .travis.yml | 2 +- ports/esp32/Makefile | 8 ++++++-- ports/unix/Makefile | 15 ++++++++++++++- ports/unix/coveragecpp.cpp | 23 +++++++++++++++++++++++ ports/unix/main.c | 2 ++ tests/unix/extra_coverage.py | 3 +++ tests/unix/extra_coverage.py.exp | 1 + 7 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 ports/unix/coveragecpp.cpp diff --git a/.travis.yml b/.travis.yml index c9fcc21336efd..3b399804e37a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -165,7 +165,7 @@ jobs: - stage: test name: "unix coverage 32-bit build and tests" install: - - sudo apt-get install gcc-multilib libffi-dev:i386 + - sudo apt-get install gcc-multilib g++-multilib libffi-dev:i386 - sudo apt-get install python3-pip - sudo pip3 install setuptools - sudo pip3 install pyelftools diff --git a/ports/esp32/Makefile b/ports/esp32/Makefile index 94374eb1c4f98..756bc8f8940c5 100644 --- a/ports/esp32/Makefile +++ b/ports/esp32/Makefile @@ -268,7 +268,7 @@ CFLAGS += -DMICROPY_ESP_IDF_4=1 endif # this is what ESPIDF uses for c++ compilation -CXXFLAGS = -std=gnu++11 $(CFLAGS_COMMON) $(INC) $(INC_ESPCOMP) +CXXFLAGS = -std=gnu++11 $(CFLAGS_COMMON) $(INC) $(INC_ESPCOMP) $(CXXFLAGS_MOD) LDFLAGS = -nostdlib -Map=$(@:.elf=.map) --cref LDFLAGS += --gc-sections -static -EL @@ -354,6 +354,9 @@ SRC_C = \ $(wildcard $(BOARD_DIR)/*.c) \ $(SRC_MOD) +SRC_CXX += \ + $(SRC_MOD_CXX) + EXTMOD_SRC_C += $(addprefix extmod/,\ modonewire.c \ ) @@ -376,6 +379,7 @@ DRIVERS_SRC_C = $(addprefix drivers/,\ OBJ_MP = OBJ_MP += $(PY_O) OBJ_MP += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) +OBJ_MP += $(addprefix $(BUILD)/, $(SRC_CXX:.cpp=.o)) OBJ_MP += $(addprefix $(BUILD)/, $(EXTMOD_SRC_C:.c=.o)) OBJ_MP += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o)) OBJ_MP += $(addprefix $(BUILD)/, $(DRIVERS_SRC_C:.c=.o)) @@ -384,7 +388,7 @@ OBJ_MP += $(addprefix $(BUILD)/, $(DRIVERS_SRC_C:.c=.o)) $(OBJ_MP): CFLAGS += -Wdouble-promotion -Wfloat-conversion # List of sources for qstr extraction -SRC_QSTR += $(SRC_C) $(EXTMOD_SRC_C) $(LIB_SRC_C) $(DRIVERS_SRC_C) +SRC_QSTR += $(SRC_C) $(SRC_CXX) $(EXTMOD_SRC_C) $(LIB_SRC_C) $(DRIVERS_SRC_C) # Append any auto-generated sources that are needed by sources listed in SRC_QSTR SRC_QSTR_AUTO_DEPS += diff --git a/ports/unix/Makefile b/ports/unix/Makefile index 7380e5e41259c..3388d67a2d6f8 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -248,13 +248,18 @@ LIB_SRC_C += $(addprefix lib/,\ utils/gchelper_generic.c \ ) +SRC_CXX += \ + coveragecpp.cpp \ + $(SRC_MOD_CXX) + OBJ = $(PY_O) OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(SRC_CXX:.cpp=.o)) OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(EXTMOD_SRC_C:.c=.o)) # List of sources for qstr extraction -SRC_QSTR += $(SRC_C) $(LIB_SRC_C) $(EXTMOD_SRC_C) +SRC_QSTR += $(SRC_C) $(SRC_CXX) $(LIB_SRC_C) $(EXTMOD_SRC_C) # Append any auto-generated sources that are needed by sources listed in # SRC_QSTR SRC_QSTR_AUTO_DEPS += @@ -272,6 +277,14 @@ ifneq ($(FROZEN_MANIFEST)$(FROZEN_DIR),) CFLAGS += -DMICROPY_MODULE_FROZEN_STR endif +HASCPP17 = $(shell expr `$(CC) -dumpversion | cut -f1 -d.` \>= 7) +ifeq ($(HASCPP17), 1) + CXXFLAGS += -std=c++17 +else + CXXFLAGS += -std=c++11 +endif +CXXFLAGS += $(filter-out -Wmissing-prototypes -Wold-style-definition -std=gnu99,$(CFLAGS) $(CXXFLAGS_MOD)) + ifeq ($(MICROPY_FORCE_32BIT),1) RUN_TESTS_MPY_CROSS_FLAGS = --mpy-cross-flags='-mcache-lookup-bc -march=x86' else diff --git a/ports/unix/coveragecpp.cpp b/ports/unix/coveragecpp.cpp new file mode 100644 index 0000000000000..ea7418e1dd46a --- /dev/null +++ b/ports/unix/coveragecpp.cpp @@ -0,0 +1,23 @@ +extern "C" { +#include "py/obj.h" +} + +#if defined(MICROPY_UNIX_COVERAGE) + +// Just to test building of C++ code. +STATIC mp_obj_t extra_cpp_coverage_impl() { + return mp_const_none; +} + +extern "C" { +mp_obj_t extra_cpp_coverage(void); +mp_obj_t extra_cpp_coverage(void) { + return extra_cpp_coverage_impl(); +} + +// This is extern to avoid name mangling. +extern const mp_obj_fun_builtin_fixed_t extra_cpp_coverage_obj = {{&mp_type_fun_builtin_0}, {extra_cpp_coverage}}; + +} + +#endif diff --git a/ports/unix/main.c b/ports/unix/main.c index 0fe492a554cbd..6f85cbf8d021d 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -531,7 +531,9 @@ MP_NOINLINE int main_(int argc, char **argv) { #if defined(MICROPY_UNIX_COVERAGE) { MP_DECLARE_CONST_FUN_OBJ_0(extra_coverage_obj); + MP_DECLARE_CONST_FUN_OBJ_0(extra_cpp_coverage_obj); mp_store_global(QSTR_FROM_STR_STATIC("extra_coverage"), MP_OBJ_FROM_PTR(&extra_coverage_obj)); + mp_store_global(QSTR_FROM_STR_STATIC("extra_cpp_coverage"), MP_OBJ_FROM_PTR(&extra_cpp_coverage_obj)); } #endif diff --git a/tests/unix/extra_coverage.py b/tests/unix/extra_coverage.py index 36105f6bad18c..1c028506e3fcc 100644 --- a/tests/unix/extra_coverage.py +++ b/tests/unix/extra_coverage.py @@ -46,6 +46,9 @@ buf = uio.BufferedWriter(stream, 8) print(buf.write(bytearray(16))) +# function defined in C++ code +print("cpp", extra_cpp_coverage()) + # test basic import of frozen scripts import frzstr1 diff --git a/tests/unix/extra_coverage.py.exp b/tests/unix/extra_coverage.py.exp index 7d7b7dd9f92f2..514ff9437bfee 100644 --- a/tests/unix/extra_coverage.py.exp +++ b/tests/unix/extra_coverage.py.exp @@ -144,6 +144,7 @@ OSError 0 None None +cpp None frzstr1 frzstr1.py frzmpy1 From 25c4563f26c2270cde01b01dca0e8b801c9c8282 Mon Sep 17 00:00:00 2001 From: stijn Date: Wed, 21 Oct 2020 11:13:47 +0200 Subject: [PATCH 047/179] examples: Add example code for user C modules, both C and C++. Add working example code to provide a starting point for users with files that they can just copy, and include the modules in the coverage test to verify the complete user C module build functionality. The cexample module uses the code originally found in cmodules.rst, which has been updated to reflect this and partially rewritten with more complete information. --- docs/develop/cmodules.rst | 160 ++++++++---------- examples/usercmodule/cexample/examplemodule.c | 34 ++++ examples/usercmodule/cexample/micropython.mk | 9 + examples/usercmodule/cppexample/example.cpp | 17 ++ .../usercmodule/cppexample/examplemodule.c | 25 +++ .../usercmodule/cppexample/examplemodule.h | 5 + .../usercmodule/cppexample/micropython.mk | 12 ++ .../unix/variants/coverage/mpconfigvariant.mk | 4 +- tests/unix/extra_coverage.py | 10 ++ tests/unix/extra_coverage.py.exp | 2 + 10 files changed, 192 insertions(+), 86 deletions(-) create mode 100644 examples/usercmodule/cexample/examplemodule.c create mode 100644 examples/usercmodule/cexample/micropython.mk create mode 100644 examples/usercmodule/cppexample/example.cpp create mode 100644 examples/usercmodule/cppexample/examplemodule.c create mode 100644 examples/usercmodule/cppexample/examplemodule.h create mode 100644 examples/usercmodule/cppexample/micropython.mk diff --git a/docs/develop/cmodules.rst b/docs/develop/cmodules.rst index 849d0e60a2ea2..2d08df82820d9 100644 --- a/docs/develop/cmodules.rst +++ b/docs/develop/cmodules.rst @@ -49,8 +49,9 @@ A MicroPython user C module is a directory with the following files: expanded copy of ``$(USERMOD_DIR)`` to ``SRC_USERMOD``, eg ``SRC_USERMOD += $(EXAMPLE_MOD_DIR)/example.c`` - If you have custom ``CFLAGS`` settings or include folders to define, these - should be added to ``CFLAGS_USERMOD``, or ``CXXFLAGS_USERMOD``. + If you have custom compiler options (like ``-I`` to add directories to search + for header files), these should be added to ``CFLAGS_USERMOD`` for C code + and to ``CXXFLAGS_USERMOD`` for C++ code. See below for full usage example. @@ -58,124 +59,113 @@ A MicroPython user C module is a directory with the following files: Basic example ------------- -This simple module named ``example`` provides a single function -``example.add_ints(a, b)`` which adds the two integer args together and returns -the result. +This simple module named ``cexample`` provides a single function +``cexample.add_ints(a, b)`` which adds the two integer args together and returns +the result. It can be found in the MicroPython source tree and has +a source file and a Makefile fragment with content as descibed above:: -Directory:: + micropython/ + └──examples/ + └──usercmodule/ + └──cexample/ + ├── examplemodule.c + └── micropython.mk - example/ - ├── example.c - └── micropython.mk +Refer to the comments in these 2 files for additional explanation. +Next to the ``cexample`` module there's also ``cppexample`` which +works in the same way but shows one way of mixing C and C++ code +in MicroPython. -``example.c`` - -.. code-block:: c - - // Include required definitions first. - #include "py/obj.h" - #include "py/runtime.h" - #include "py/builtin.h" - - // This is the function which will be called from Python as example.add_ints(a, b). - STATIC mp_obj_t example_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) { - // Extract the ints from the micropython input objects - int a = mp_obj_get_int(a_obj); - int b = mp_obj_get_int(b_obj); +Compiling the cmodule into MicroPython +-------------------------------------- - // Calculate the addition and convert to MicroPython object. - return mp_obj_new_int(a + b); - } - // Define a Python reference to the function above - STATIC MP_DEFINE_CONST_FUN_OBJ_2(example_add_ints_obj, example_add_ints); +To build such a module, compile MicroPython (see `getting started +`_), +applying 2 modifications: - // Define all properties of the example module. - // Table entries are key/value pairs of the attribute name (a string) - // and the MicroPython object reference. - // All identifiers and strings are written as MP_QSTR_xxx and will be - // optimized to word-sized integers by the build system (interned strings). - STATIC const mp_rom_map_elem_t example_module_globals_table[] = { - { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_example) }, - { MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&example_add_ints_obj) }, - }; - STATIC MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table); +- an extra ``make`` flag named ``USER_C_MODULES`` set to the directory + containing all modules you want included (not to the module itself). + For building the example modules which come with MicroPython, + set ``USER_C_MODULES`` to the ``examples/usercmodule`` directory. + For your own projects it's more convenient to keep custom code out of + the main source tree so a typical project directory structure will look + like this:: - // Define module object. - const mp_obj_module_t example_user_cmodule = { - .base = { &mp_type_module }, - .globals = (mp_obj_dict_t*)&example_module_globals, - }; + my_project/ + ├── modules/ + │ └──example1/ + │ ├──example1.c + │ └──micropython.mk + │ └──example2/ + │ ├──example2.c + │ └──micropython.mk + └── micropython/ + ├──ports/ + ... ├──stm32/ + ... - // Register the module to make it available in Python - MP_REGISTER_MODULE(MP_QSTR_example, example_user_cmodule, MODULE_EXAMPLE_ENABLED); + with ``USER_C_MODULES`` set to the ``my_project/modules`` directory. -``micropython.mk`` +- all modules found in this directory will be compiled, but only those + which are explicitly enabled will be availabe for importing. Enabling a + module is done by setting the preprocessor define from its module + registration to 1. For example if the source code defines the module with -.. code-block:: make + .. code-block:: c - EXAMPLE_MOD_DIR := $(USERMOD_DIR) + MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule, MODULE_CEXAMPLE_ENABLED); - # Add all C files to SRC_USERMOD. - SRC_USERMOD += $(EXAMPLE_MOD_DIR)/example.c - # We can add our module folder to include paths if needed - # This is not actually needed in this example. - CFLAGS_USERMOD += -I$(EXAMPLE_MOD_DIR) + then ``MODULE_CEXAMPLE_ENABLED`` has to be set to 1 to make the module available. + This can be done by adding ``CFLAGS_EXTRA=-DMODULE_CEXAMPLE_ENABLED=1`` to + the ``make`` command, or editing ``mpconfigport.h`` or ``mpconfigboard.h`` + to add -Finally you will need to define ``MODULE_EXAMPLE_ENABLED`` to 1. This -can be done by adding ``CFLAGS_EXTRA=-DMODULE_EXAMPLE_ENABLED=1`` to -the ``make`` command, or editing ``mpconfigport.h`` or -``mpconfigboard.h`` to add + .. code-block:: c -.. code-block:: c + #define MODULE_CEXAMPLE_ENABLED (1) - #define MODULE_EXAMPLE_ENABLED (1) -Note that the exact method depends on the port as they have different -structures. If not done correctly it will compile but importing will -fail to find the module. + Note that the exact method depends on the port as they have different + structures. If not done correctly it will compile but importing will + fail to find the module. +To sum up, here's how the ``cexample`` module from the ``examples/usercmodule`` +directory can be built for the unix port: -Compiling the cmodule into MicroPython --------------------------------------- +.. code-block:: bash -To build such a module, compile MicroPython (see `getting started -`_) with an -extra ``make`` flag named ``USER_C_MODULES`` set to the directory containing -all modules you want included (not to the module itself). For example: + cd micropython/ports/unix + make USER_C_MODULES=../../examples/usercmodule CFLAGS_EXTRA=-DMODULE_CEXAMPLE_ENABLED=1 all +The build output will show the modules found:: -Directory:: + ... + Including User C Module from ../../examples/usercmodule/cexample + Including User C Module from ../../examples/usercmodule/cppexample + ... - my_project/ - ├── modules/ - │ └──example/ - │ ├──example.c - │ └──micropython.mk - └── micropython/ - ├──ports/ - ... ├──stm32/ - ... -Building for stm32 port: +Or for your own project with a directory structure as shown above, +including both modules and building the stm32 port for example: .. code-block:: bash cd my_project/micropython/ports/stm32 - make USER_C_MODULES=../../../modules CFLAGS_EXTRA=-DMODULE_EXAMPLE_ENABLED=1 all + make USER_C_MODULES=../../../modules \ + CFLAGS_EXTRA="-DMODULE_EXAMPLE1_ENABLED=1 -DMODULE_EXAMPLE2_ENABLED=1" all Module usage in MicroPython --------------------------- -Once built into your copy of MicroPython, the module implemented -in ``example.c`` above can now be accessed in Python just -like any other builtin module, eg +Once built into your copy of MicroPython, the module +can now be accessed in Python just like any other builtin module, e.g. .. code-block:: python - import example - print(example.add_ints(1, 3)) + import cexample + print(cexample.add_ints(1, 3)) # should display 4 diff --git a/examples/usercmodule/cexample/examplemodule.c b/examples/usercmodule/cexample/examplemodule.c new file mode 100644 index 0000000000000..f608823c9e51c --- /dev/null +++ b/examples/usercmodule/cexample/examplemodule.c @@ -0,0 +1,34 @@ +// Include MicroPython API. +#include "py/runtime.h" + +// This is the function which will be called from Python as cexample.add_ints(a, b). +STATIC mp_obj_t example_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) { + // Extract the ints from the micropython input objects. + int a = mp_obj_get_int(a_obj); + int b = mp_obj_get_int(b_obj); + + // Calculate the addition and convert to MicroPython object. + return mp_obj_new_int(a + b); +} +// Define a Python reference to the function above. +STATIC MP_DEFINE_CONST_FUN_OBJ_2(example_add_ints_obj, example_add_ints); + +// Define all properties of the module. +// Table entries are key/value pairs of the attribute name (a string) +// and the MicroPython object reference. +// All identifiers and strings are written as MP_QSTR_xxx and will be +// optimized to word-sized integers by the build system (interned strings). +STATIC const mp_rom_map_elem_t example_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_cexample) }, + { MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&example_add_ints_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table); + +// Define module object. +const mp_obj_module_t example_user_cmodule = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&example_module_globals, +}; + +// Register the module to make it available in Python. +MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule, MODULE_CEXAMPLE_ENABLED); diff --git a/examples/usercmodule/cexample/micropython.mk b/examples/usercmodule/cexample/micropython.mk new file mode 100644 index 0000000000000..dbfe3c5cbdb1f --- /dev/null +++ b/examples/usercmodule/cexample/micropython.mk @@ -0,0 +1,9 @@ +EXAMPLE_MOD_DIR := $(USERMOD_DIR) + +# Add all C files to SRC_USERMOD. +SRC_USERMOD += $(EXAMPLE_MOD_DIR)/examplemodule.c + +# We can add our module folder to include paths if needed +# This is not actually needed in this example. +CFLAGS_USERMOD += -I$(EXAMPLE_MOD_DIR) +CEXAMPLE_MOD_DIR := $(USERMOD_DIR) diff --git a/examples/usercmodule/cppexample/example.cpp b/examples/usercmodule/cppexample/example.cpp new file mode 100644 index 0000000000000..06809732a4d7b --- /dev/null +++ b/examples/usercmodule/cppexample/example.cpp @@ -0,0 +1,17 @@ +extern "C" { +#include + +// Here we implement the function using C++ code, but since it's +// declaration has to be compatible with C everything goes in extern "C" scope. +mp_obj_t cppfunc(mp_obj_t a_obj, mp_obj_t b_obj) { + // Prove we have (at least) C++11 features. + const auto a = mp_obj_get_int(a_obj); + const auto b = mp_obj_get_int(b_obj); + const auto sum = [&]() { + return mp_obj_new_int(a + b); + } (); + // Prove we're being scanned for QSTRs. + mp_obj_t tup[] = {sum, MP_ROM_QSTR(MP_QSTR_hellocpp)}; + return mp_obj_new_tuple(2, tup); +} +} diff --git a/examples/usercmodule/cppexample/examplemodule.c b/examples/usercmodule/cppexample/examplemodule.c new file mode 100644 index 0000000000000..ceb588bef6b30 --- /dev/null +++ b/examples/usercmodule/cppexample/examplemodule.c @@ -0,0 +1,25 @@ +#include + +// Define a Python reference to the function we'll make available. +// See example.cpp for the definition. +STATIC MP_DEFINE_CONST_FUN_OBJ_2(cppfunc_obj, cppfunc); + +// Define all properties of the module. +// Table entries are key/value pairs of the attribute name (a string) +// and the MicroPython object reference. +// All identifiers and strings are written as MP_QSTR_xxx and will be +// optimized to word-sized integers by the build system (interned strings). +STATIC const mp_rom_map_elem_t cppexample_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_cppexample) }, + { MP_ROM_QSTR(MP_QSTR_cppfunc), MP_ROM_PTR(&cppfunc_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(cppexample_module_globals, cppexample_module_globals_table); + +// Define module object. +const mp_obj_module_t cppexample_user_cmodule = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&cppexample_module_globals, +}; + +// Register the module to make it available in Python. +MP_REGISTER_MODULE(MP_QSTR_cppexample, cppexample_user_cmodule, MODULE_CPPEXAMPLE_ENABLED); diff --git a/examples/usercmodule/cppexample/examplemodule.h b/examples/usercmodule/cppexample/examplemodule.h new file mode 100644 index 0000000000000..d89384a630060 --- /dev/null +++ b/examples/usercmodule/cppexample/examplemodule.h @@ -0,0 +1,5 @@ +// Include MicroPython API. +#include "py/runtime.h" + +// Declare the function we'll make available in Python as cppexample.cppfunc(). +extern mp_obj_t cppfunc(mp_obj_t a_obj, mp_obj_t b_obj); diff --git a/examples/usercmodule/cppexample/micropython.mk b/examples/usercmodule/cppexample/micropython.mk new file mode 100644 index 0000000000000..e10d965a0011e --- /dev/null +++ b/examples/usercmodule/cppexample/micropython.mk @@ -0,0 +1,12 @@ +CPPEXAMPLE_MOD_DIR := $(USERMOD_DIR) + +# Add our source files to the respective variables. +SRC_USERMOD += $(CPPEXAMPLE_MOD_DIR)/examplemodule.c +SRC_USERMOD_CXX += $(CPPEXAMPLE_MOD_DIR)/example.cpp + +# Add our module directory to the include path. +CFLAGS_USERMOD += -I$(CPPEXAMPLE_MOD_DIR) +CXXFLAGS_USERMOD += -I$(CPPEXAMPLE_MOD_DIR) + +# We use C++ features so have to link against the standard library. +LDFLAGS_USERMOD += -lstdc++ diff --git a/ports/unix/variants/coverage/mpconfigvariant.mk b/ports/unix/variants/coverage/mpconfigvariant.mk index f11d0b0d28f75..55399831aaa0a 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.mk +++ b/ports/unix/variants/coverage/mpconfigvariant.mk @@ -7,11 +7,13 @@ CFLAGS += \ -fprofile-arcs -ftest-coverage \ -Wformat -Wmissing-declarations -Wmissing-prototypes \ -Wold-style-definition -Wpointer-arith -Wshadow -Wuninitialized -Wunused-parameter \ - -DMICROPY_UNIX_COVERAGE + -DMICROPY_UNIX_COVERAGE \ + -DMODULE_CEXAMPLE_ENABLED=1 -DMODULE_CPPEXAMPLE_ENABLED=1 LDFLAGS += -fprofile-arcs -ftest-coverage FROZEN_MANIFEST ?= $(VARIANT_DIR)/manifest.py +USER_C_MODULES = $(TOP)/examples/usercmodule MICROPY_ROM_TEXT_COMPRESSION = 1 MICROPY_VFS_FAT = 1 diff --git a/tests/unix/extra_coverage.py b/tests/unix/extra_coverage.py index 1c028506e3fcc..b4808993a760e 100644 --- a/tests/unix/extra_coverage.py +++ b/tests/unix/extra_coverage.py @@ -49,6 +49,16 @@ # function defined in C++ code print("cpp", extra_cpp_coverage()) +# test user C module +import cexample + +print(cexample.add_ints(3, 2)) + +# test user C module mixed with C++ code +import cppexample + +print(cppexample.cppfunc(1, 2)) + # test basic import of frozen scripts import frzstr1 diff --git a/tests/unix/extra_coverage.py.exp b/tests/unix/extra_coverage.py.exp index 514ff9437bfee..257224108a4c5 100644 --- a/tests/unix/extra_coverage.py.exp +++ b/tests/unix/extra_coverage.py.exp @@ -145,6 +145,8 @@ OSError None None cpp None +5 +(3, 'hellocpp') frzstr1 frzstr1.py frzmpy1 From dbb13104ca612d1b2b289adc51c028a1bd1f7d1d Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 29 Oct 2020 17:32:39 +1100 Subject: [PATCH 048/179] docs/develop/cmodules.rst: Add link to source code for user C example. Signed-off-by: Damien George --- docs/develop/cmodules.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/develop/cmodules.rst b/docs/develop/cmodules.rst index 2d08df82820d9..2db1f65f2e488 100644 --- a/docs/develop/cmodules.rst +++ b/docs/develop/cmodules.rst @@ -61,8 +61,9 @@ Basic example This simple module named ``cexample`` provides a single function ``cexample.add_ints(a, b)`` which adds the two integer args together and returns -the result. It can be found in the MicroPython source tree and has -a source file and a Makefile fragment with content as descibed above:: +the result. It can be found in the MicroPython source tree +`in the examples directory `_ +and has a source file and a Makefile fragment with content as descibed above:: micropython/ └──examples/ From a866f868f87e50b9aa86a2f2bec8cfc9fb687437 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 29 Oct 2020 17:33:34 +1100 Subject: [PATCH 049/179] unix/Makefile: Move coverage.c and coveragecpp.cpp to coverage variant. So that g++ is not needed to build a non-coverage unix variant. Signed-off-by: Damien George --- ports/unix/Makefile | 4 +--- ports/unix/variants/coverage/mpconfigvariant.mk | 3 +++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ports/unix/Makefile b/ports/unix/Makefile index 3388d67a2d6f8..048ef97f77531 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -220,7 +220,7 @@ SRC_MOD += modjni.c endif # source files -SRC_C = \ +SRC_C += \ main.c \ gccollect.c \ unix_mphal.c \ @@ -232,7 +232,6 @@ SRC_C = \ modtime.c \ moduselect.c \ alloc.c \ - coverage.c \ fatfs_port.c \ mpbthciport.c \ mpbtstackport_common.c \ @@ -249,7 +248,6 @@ LIB_SRC_C += $(addprefix lib/,\ ) SRC_CXX += \ - coveragecpp.cpp \ $(SRC_MOD_CXX) OBJ = $(PY_O) diff --git a/ports/unix/variants/coverage/mpconfigvariant.mk b/ports/unix/variants/coverage/mpconfigvariant.mk index 55399831aaa0a..ef81975d9da8c 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.mk +++ b/ports/unix/variants/coverage/mpconfigvariant.mk @@ -19,3 +19,6 @@ MICROPY_ROM_TEXT_COMPRESSION = 1 MICROPY_VFS_FAT = 1 MICROPY_VFS_LFS1 = 1 MICROPY_VFS_LFS2 = 1 + +SRC_C += coverage.c +SRC_CXX += coveragecpp.cpp From df3b466d6c44af494e404e54861b4c25cf4d54c8 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 29 Oct 2020 23:07:51 +1100 Subject: [PATCH 050/179] stm32/boards: Factor out common data/bss/heap/stack linker sections. Signed-off-by: Damien George --- ports/stm32/boards/PYBD_SF2/f722_qspi.ld | 37 +------------- ports/stm32/boards/PYBD_SF6/f767.ld | 36 +------------- ports/stm32/boards/STM32F769DISC/f769_qspi.ld | 41 +--------------- ports/stm32/boards/common_basic.ld | 48 +------------------ ports/stm32/boards/common_bl.ld | 48 +------------------ ports/stm32/boards/common_blifs.ld | 48 +------------------ ports/stm32/boards/common_bss_heap_stack.ld | 28 +++++++++++ .../boards/common_extratext_data_in_flash.ld | 14 ++++++ .../common_extratext_data_in_flash_app.ld | 14 ++++++ .../common_extratext_data_in_flash_text.ld | 14 ++++++ ports/stm32/boards/common_ifs.ld | 48 +------------------ 11 files changed, 84 insertions(+), 292 deletions(-) create mode 100644 ports/stm32/boards/common_bss_heap_stack.ld create mode 100644 ports/stm32/boards/common_extratext_data_in_flash.ld create mode 100644 ports/stm32/boards/common_extratext_data_in_flash_app.ld create mode 100644 ports/stm32/boards/common_extratext_data_in_flash_text.ld diff --git a/ports/stm32/boards/PYBD_SF2/f722_qspi.ld b/ports/stm32/boards/PYBD_SF2/f722_qspi.ld index 3b2e45d9b6a11..c9199b341bfd4 100644 --- a/ports/stm32/boards/PYBD_SF2/f722_qspi.ld +++ b/ports/stm32/boards/PYBD_SF2/f722_qspi.ld @@ -72,39 +72,6 @@ SECTIONS _etext = .; } >FLASH_APP - _sidata = LOADADDR(.data); - - .data : - { - . = ALIGN(4); - _sdata = .; - *(.data*) - - . = ALIGN(4); - _edata = .; - } >RAM AT> FLASH_APP - - .bss : - { - . = ALIGN(4); - _sbss = .; - *(.bss*) - *(COMMON) - . = ALIGN(4); - _ebss = .; - } >RAM - - .heap : - { - . = ALIGN(4); - . = . + _minimum_heap_size; - . = ALIGN(4); - } >RAM - - .stack : - { - . = ALIGN(4); - . = . + _minimum_stack_size; - . = ALIGN(4); - } >RAM + INCLUDE common_extratext_data_in_flash_app.ld + INCLUDE common_bss_heap_stack.ld } diff --git a/ports/stm32/boards/PYBD_SF6/f767.ld b/ports/stm32/boards/PYBD_SF6/f767.ld index 1dd4c11ed9898..5866f0b5cc2d5 100644 --- a/ports/stm32/boards/PYBD_SF6/f767.ld +++ b/ports/stm32/boards/PYBD_SF6/f767.ld @@ -62,38 +62,6 @@ SECTIONS _etext = .; } >FLASH_APP - _sidata = LOADADDR(.data); - - .data : - { - . = ALIGN(4); - _sdata = .; - *(.data*) - . = ALIGN(4); - _edata = .; - } >RAM AT> FLASH_APP - - .bss : - { - . = ALIGN(4); - _sbss = .; - *(.bss*) - *(COMMON) - . = ALIGN(4); - _ebss = .; - } >RAM - - .heap : - { - . = ALIGN(4); - . = . + _minimum_heap_size; - . = ALIGN(4); - } >RAM - - .stack : - { - . = ALIGN(4); - . = . + _minimum_stack_size; - . = ALIGN(4); - } >RAM + INCLUDE common_extratext_data_in_flash_app.ld + INCLUDE common_bss_heap_stack.ld } diff --git a/ports/stm32/boards/STM32F769DISC/f769_qspi.ld b/ports/stm32/boards/STM32F769DISC/f769_qspi.ld index 5f920b417fdde..b6957a321332c 100644 --- a/ports/stm32/boards/STM32F769DISC/f769_qspi.ld +++ b/ports/stm32/boards/STM32F769DISC/f769_qspi.ld @@ -68,43 +68,6 @@ SECTIONS _etext = .; } >FLASH_APP - /* Used by the startup to initialize data */ - _sidata = LOADADDR(.data); - - /* The initialized data section */ - .data : - { - . = ALIGN(4); - _sdata = .; - *(.data*) - . = ALIGN(4); - _edata = .; - } >RAM AT> FLASH_APP - - /* The uninitialized (zeroed) data section */ - .bss : - { - . = ALIGN(4); - _sbss = .; - *(.bss*) - *(COMMON) - . = ALIGN(4); - _ebss = .; - } >RAM - - /* Define the start of the heap, and make sure we have a minimum size */ - .heap : - { - . = ALIGN(4); - . = . + _minimum_heap_size; - . = ALIGN(4); - } >RAM - - /* Just checks there is enough RAM for the stack */ - .stack : - { - . = ALIGN(4); - . = . + _minimum_stack_size; - . = ALIGN(4); - } >RAM + INCLUDE common_extratext_data_in_flash_app.ld + INCLUDE common_bss_heap_stack.ld } diff --git a/ports/stm32/boards/common_basic.ld b/ports/stm32/boards/common_basic.ld index 2e428aa62c573..dbda1b8b68ba4 100644 --- a/ports/stm32/boards/common_basic.ld +++ b/ports/stm32/boards/common_basic.ld @@ -37,50 +37,6 @@ SECTIONS _etext = .; /* define a global symbol at end of code */ } >FLASH - /* used by the startup to initialize data */ - _sidata = LOADADDR(.data); - - /* This is the initialized data section - The program executes knowing that the data is in the RAM - but the loader puts the initial values in the FLASH (inidata). - It is one task of the startup to copy the initial values from FLASH to RAM. */ - .data : - { - . = ALIGN(4); - _sdata = .; /* create a global symbol at data start; used by startup code in order to initialise the .data section in RAM */ - *(.data*) /* .data* sections */ - - . = ALIGN(4); - _edata = .; /* define a global symbol at data end; used by startup code in order to initialise the .data section in RAM */ - } >RAM AT> FLASH - - /* Uninitialized data section */ - .bss : - { - . = ALIGN(4); - _sbss = .; /* define a global symbol at bss start; used by startup code */ - *(.bss*) - *(COMMON) - - . = ALIGN(4); - _ebss = .; /* define a global symbol at bss end; used by startup code and GC */ - } >RAM - - /* this is to define the start of the heap, and make sure we have a minimum size */ - .heap : - { - . = ALIGN(4); - . = . + _minimum_heap_size; - . = ALIGN(4); - } >RAM - - /* this just checks there is enough RAM for the stack */ - .stack : - { - . = ALIGN(4); - . = . + _minimum_stack_size; - . = ALIGN(4); - } >RAM - - .ARM.attributes 0 : { *(.ARM.attributes) } + INCLUDE common_extratext_data_in_flash.ld + INCLUDE common_bss_heap_stack.ld } diff --git a/ports/stm32/boards/common_bl.ld b/ports/stm32/boards/common_bl.ld index 52b2a677d706d..21d809a3d2339 100644 --- a/ports/stm32/boards/common_bl.ld +++ b/ports/stm32/boards/common_bl.ld @@ -37,50 +37,6 @@ SECTIONS _etext = .; /* define a global symbol at end of code */ } >FLASH_APP - /* used by the startup to initialize data */ - _sidata = LOADADDR(.data); - - /* This is the initialized data section - The program executes knowing that the data is in the RAM - but the loader puts the initial values in the FLASH (inidata). - It is one task of the startup to copy the initial values from FLASH to RAM. */ - .data : - { - . = ALIGN(4); - _sdata = .; /* create a global symbol at data start; used by startup code in order to initialise the .data section in RAM */ - *(.data*) /* .data* sections */ - - . = ALIGN(4); - _edata = .; /* define a global symbol at data end; used by startup code in order to initialise the .data section in RAM */ - } >RAM AT> FLASH_APP - - /* Uninitialized data section */ - .bss : - { - . = ALIGN(4); - _sbss = .; /* define a global symbol at bss start; used by startup code */ - *(.bss*) - *(COMMON) - - . = ALIGN(4); - _ebss = .; /* define a global symbol at bss end; used by startup code and GC */ - } >RAM - - /* this is to define the start of the heap, and make sure we have a minimum size */ - .heap : - { - . = ALIGN(4); - . = . + _minimum_heap_size; - . = ALIGN(4); - } >RAM - - /* this just checks there is enough RAM for the stack */ - .stack : - { - . = ALIGN(4); - . = . + _minimum_stack_size; - . = ALIGN(4); - } >RAM - - .ARM.attributes 0 : { *(.ARM.attributes) } + INCLUDE common_extratext_data_in_flash_app.ld + INCLUDE common_bss_heap_stack.ld } diff --git a/ports/stm32/boards/common_blifs.ld b/ports/stm32/boards/common_blifs.ld index 65722f2e57e8d..5517a2d09c69c 100644 --- a/ports/stm32/boards/common_blifs.ld +++ b/ports/stm32/boards/common_blifs.ld @@ -37,50 +37,6 @@ SECTIONS _etext = .; /* define a global symbol at end of code */ } >FLASH_TEXT - /* used by the startup to initialize data */ - _sidata = LOADADDR(.data); - - /* This is the initialized data section - The program executes knowing that the data is in the RAM - but the loader puts the initial values in the FLASH (inidata). - It is one task of the startup to copy the initial values from FLASH to RAM. */ - .data : - { - . = ALIGN(4); - _sdata = .; /* create a global symbol at data start; used by startup code in order to initialise the .data section in RAM */ - *(.data*) /* .data* sections */ - - . = ALIGN(4); - _edata = .; /* define a global symbol at data end; used by startup code in order to initialise the .data section in RAM */ - } >RAM AT> FLASH_TEXT - - /* Uninitialized data section */ - .bss : - { - . = ALIGN(4); - _sbss = .; /* define a global symbol at bss start; used by startup code */ - *(.bss*) - *(COMMON) - - . = ALIGN(4); - _ebss = .; /* define a global symbol at bss end; used by startup code and GC */ - } >RAM - - /* this is to define the start of the heap, and make sure we have a minimum size */ - .heap : - { - . = ALIGN(4); - . = . + _minimum_heap_size; - . = ALIGN(4); - } >RAM - - /* this just checks there is enough RAM for the stack */ - .stack : - { - . = ALIGN(4); - . = . + _minimum_stack_size; - . = ALIGN(4); - } >RAM - - .ARM.attributes 0 : { *(.ARM.attributes) } + INCLUDE common_extratext_data_in_flash_text.ld + INCLUDE common_bss_heap_stack.ld } diff --git a/ports/stm32/boards/common_bss_heap_stack.ld b/ports/stm32/boards/common_bss_heap_stack.ld new file mode 100644 index 0000000000000..1bb2249e96c9b --- /dev/null +++ b/ports/stm32/boards/common_bss_heap_stack.ld @@ -0,0 +1,28 @@ +/* This linker script fragment is intended to be included in SECTIONS. */ + +/* Zeroed-out data section */ +.bss : +{ + . = ALIGN(4); + _sbss = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _ebss = .; +} >RAM + +/* This is to define the start of the heap, and make sure there is a minimum size */ +.heap : +{ + . = ALIGN(4); + . = . + _minimum_heap_size; + . = ALIGN(4); +} >RAM + +/* This checks there is enough RAM for the stack */ +.stack : +{ + . = ALIGN(4); + . = . + _minimum_stack_size; + . = ALIGN(4); +} >RAM diff --git a/ports/stm32/boards/common_extratext_data_in_flash.ld b/ports/stm32/boards/common_extratext_data_in_flash.ld new file mode 100644 index 0000000000000..e5f25a3c6f649 --- /dev/null +++ b/ports/stm32/boards/common_extratext_data_in_flash.ld @@ -0,0 +1,14 @@ +/* This linker script fragment is intended to be included in SECTIONS. */ + +/* Used by the start-up code to initialise data */ +_sidata = LOADADDR(.data); + +/* Initialised data section, start-up code will copy it from flash to RAM */ +.data : +{ + . = ALIGN(4); + _sdata = .; + *(.data*) + . = ALIGN(4); + _edata = .; +} >RAM AT> FLASH diff --git a/ports/stm32/boards/common_extratext_data_in_flash_app.ld b/ports/stm32/boards/common_extratext_data_in_flash_app.ld new file mode 100644 index 0000000000000..8230f8f9b2c38 --- /dev/null +++ b/ports/stm32/boards/common_extratext_data_in_flash_app.ld @@ -0,0 +1,14 @@ +/* This linker script fragment is intended to be included in SECTIONS. */ + +/* Used by the start-up code to initialise data */ +_sidata = LOADADDR(.data); + +/* Initialised data section, start-up code will copy it from flash to RAM */ +.data : +{ + . = ALIGN(4); + _sdata = .; + *(.data*) + . = ALIGN(4); + _edata = .; +} >RAM AT> FLASH_APP diff --git a/ports/stm32/boards/common_extratext_data_in_flash_text.ld b/ports/stm32/boards/common_extratext_data_in_flash_text.ld new file mode 100644 index 0000000000000..526d2519f7bff --- /dev/null +++ b/ports/stm32/boards/common_extratext_data_in_flash_text.ld @@ -0,0 +1,14 @@ +/* This linker script fragment is intended to be included in SECTIONS. */ + +/* Used by the start-up code to initialise data */ +_sidata = LOADADDR(.data); + +/* Initialised data section, start-up code will copy it from flash to RAM */ +.data : +{ + . = ALIGN(4); + _sdata = .; + *(.data*) + . = ALIGN(4); + _edata = .; +} >RAM AT> FLASH_TEXT diff --git a/ports/stm32/boards/common_ifs.ld b/ports/stm32/boards/common_ifs.ld index 74b2ffb419406..733ca12f675bf 100644 --- a/ports/stm32/boards/common_ifs.ld +++ b/ports/stm32/boards/common_ifs.ld @@ -54,50 +54,6 @@ SECTIONS _etext = .; /* define a global symbol at end of code */ } >FLASH_TEXT - /* used by the startup to initialize data */ - _sidata = LOADADDR(.data); - - /* This is the initialized data section - The program executes knowing that the data is in the RAM - but the loader puts the initial values in the FLASH (inidata). - It is one task of the startup to copy the initial values from FLASH to RAM. */ - .data : - { - . = ALIGN(4); - _sdata = .; /* create a global symbol at data start; used by startup code in order to initialise the .data section in RAM */ - *(.data*) /* .data* sections */ - - . = ALIGN(4); - _edata = .; /* define a global symbol at data end; used by startup code in order to initialise the .data section in RAM */ - } >RAM AT> FLASH_TEXT - - /* Uninitialized data section */ - .bss : - { - . = ALIGN(4); - _sbss = .; /* define a global symbol at bss start; used by startup code */ - *(.bss*) - *(COMMON) - - . = ALIGN(4); - _ebss = .; /* define a global symbol at bss end; used by startup code and GC */ - } >RAM - - /* this is to define the start of the heap, and make sure we have a minimum size */ - .heap : - { - . = ALIGN(4); - . = . + _minimum_heap_size; - . = ALIGN(4); - } >RAM - - /* this just checks there is enough RAM for the stack */ - .stack : - { - . = ALIGN(4); - . = . + _minimum_stack_size; - . = ALIGN(4); - } >RAM - - .ARM.attributes 0 : { *(.ARM.attributes) } + INCLUDE common_extratext_data_in_flash_text.ld + INCLUDE common_bss_heap_stack.ld } From 97960dc7deb7a0e691fca5944402cd03386b744b Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 29 Oct 2020 17:34:38 +1100 Subject: [PATCH 051/179] stm32: Support C++ code and user C modules written in C++. Also build user C modules as part of the stm32 CI. Signed-off-by: Damien George --- .travis.yml | 2 +- ports/stm32/Makefile | 13 ++++++++++++- .../stm32/boards/common_extratext_data_in_flash.ld | 8 ++++++++ .../boards/common_extratext_data_in_flash_app.ld | 8 ++++++++ .../boards/common_extratext_data_in_flash_text.ld | 8 ++++++++ ports/stm32/main.c | 4 ++++ 6 files changed, 41 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3b399804e37a3..14595ddeb1d39 100644 --- a/.travis.yml +++ b/.travis.yml @@ -99,7 +99,7 @@ jobs: - make ${MAKEOPTS} -C ports/stm32 submodules - git submodule update --init lib/btstack - make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_F091RC - - make ${MAKEOPTS} -C ports/stm32 BOARD=PYBV11 MICROPY_PY_WIZNET5K=5200 MICROPY_PY_CC3K=1 + - make ${MAKEOPTS} -C ports/stm32 BOARD=PYBV11 MICROPY_PY_WIZNET5K=5200 MICROPY_PY_CC3K=1 USER_C_MODULES=../../examples/usercmodule CFLAGS_EXTRA="-DMODULE_CEXAMPLE_ENABLED=1 -DMODULE_CPPEXAMPLE_ENABLED=1" - make ${MAKEOPTS} -C ports/stm32 BOARD=PYBD_SF2 - make ${MAKEOPTS} -C ports/stm32 BOARD=PYBD_SF6 NANBOX=1 MICROPY_BLUETOOTH_NIMBLE=0 MICROPY_BLUETOOTH_BTSTACK=1 - make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_H743ZI CFLAGS_EXTRA='-DMICROPY_PY_THREAD=1' diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index cf3a589ca9087..61da9cc9807aa 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -138,6 +138,13 @@ else COPT += -Os -DNDEBUG endif +# Flags for optional C++ source code +CXXFLAGS += $(filter-out -Wmissing-prototypes -Wold-style-definition -std=gnu99,$(CFLAGS)) +CXXFLAGS += $(CXXFLAGS_MOD) +ifneq ($(SRC_CXX)$(SRC_MOD_CXX),) +LDFLAGS += -L$(dir $(shell $(CXX) $(CXXFLAGS) -print-file-name=libstdc++.a)) +endif + # Options for mpy-cross MPY_CROSS_FLAGS += -march=armv7m @@ -330,6 +337,9 @@ SRC_C += \ adc.c \ $(wildcard $(BOARD_DIR)/*.c) +SRC_CXX += \ + $(SRC_MOD_CXX) + SRC_O += \ $(STARTUP_FILE) \ $(SYSTEM_FILE) @@ -511,6 +521,7 @@ OBJ += $(LIBM_O) OBJ += $(addprefix $(BUILD)/, $(EXTMOD_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(DRIVERS_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(SRC_CXX:.cpp=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_O)) OBJ += $(addprefix $(BUILD)/, $(SRC_HAL:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_USBDEV:.c=.o)) @@ -644,7 +655,7 @@ GEN_CDCINF_FILE = $(HEADER_BUILD)/pybcdc.inf GEN_CDCINF_HEADER = $(HEADER_BUILD)/pybcdc_inf.h # List of sources for qstr extraction -SRC_QSTR += $(SRC_C) $(SRC_MOD) $(LIB_SRC_C) $(EXTMOD_SRC_C) +SRC_QSTR += $(SRC_C) $(SRC_CXX) $(SRC_MOD) $(LIB_SRC_C) $(EXTMOD_SRC_C) # Append any auto-generated sources that are needed by sources listed in # SRC_QSTR SRC_QSTR_AUTO_DEPS += $(GEN_CDCINF_HEADER) diff --git a/ports/stm32/boards/common_extratext_data_in_flash.ld b/ports/stm32/boards/common_extratext_data_in_flash.ld index e5f25a3c6f649..eb9b86f49d8e0 100644 --- a/ports/stm32/boards/common_extratext_data_in_flash.ld +++ b/ports/stm32/boards/common_extratext_data_in_flash.ld @@ -1,5 +1,13 @@ /* This linker script fragment is intended to be included in SECTIONS. */ +/* For C++ exception handling */ +.ARM : +{ + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; +} >FLASH + /* Used by the start-up code to initialise data */ _sidata = LOADADDR(.data); diff --git a/ports/stm32/boards/common_extratext_data_in_flash_app.ld b/ports/stm32/boards/common_extratext_data_in_flash_app.ld index 8230f8f9b2c38..aba6bf57c88af 100644 --- a/ports/stm32/boards/common_extratext_data_in_flash_app.ld +++ b/ports/stm32/boards/common_extratext_data_in_flash_app.ld @@ -1,5 +1,13 @@ /* This linker script fragment is intended to be included in SECTIONS. */ +/* For C++ exception handling */ +.ARM : +{ + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; +} >FLASH_APP + /* Used by the start-up code to initialise data */ _sidata = LOADADDR(.data); diff --git a/ports/stm32/boards/common_extratext_data_in_flash_text.ld b/ports/stm32/boards/common_extratext_data_in_flash_text.ld index 526d2519f7bff..5a29e47307a6b 100644 --- a/ports/stm32/boards/common_extratext_data_in_flash_text.ld +++ b/ports/stm32/boards/common_extratext_data_in_flash_text.ld @@ -1,5 +1,13 @@ /* This linker script fragment is intended to be included in SECTIONS. */ +/* For C++ exception handling */ +.ARM : +{ + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; +} >FLASH_TEXT + /* Used by the start-up code to initialise data */ _sidata = LOADADDR(.data); diff --git a/ports/stm32/main.c b/ports/stm32/main.c index b5dbfa50fd5cc..f19dac0e7947b 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -133,6 +133,10 @@ void nlr_jump_fail(void *val) { __fatal_error(""); } +void abort(void) { + __fatal_error("abort"); +} + #ifndef NDEBUG void MP_WEAK __assert_func(const char *file, int line, const char *func, const char *expr) { (void)func; From ed7ddd4dd436fb84e602fee4dbdc4882eca642ab Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 29 Oct 2020 17:36:04 +1100 Subject: [PATCH 052/179] tests/micropython/extreme_exc.py: Unlink alloc'd lists earlier in chain. To help the GC collect this memory that's no longer needed after the test. Signed-off-by: Damien George --- tests/micropython/extreme_exc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/micropython/extreme_exc.py b/tests/micropython/extreme_exc.py index dae5b151860fc..9d2f24745f933 100644 --- a/tests/micropython/extreme_exc.py +++ b/tests/micropython/extreme_exc.py @@ -126,7 +126,7 @@ def f(): ) except Exception as er: e = er - lst[0] = None + lst[0][0] = None lst = None print(repr(e)[:10]) From 2ae3c890bd923b4c39bba3d2e2f2d75eca5dcc06 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Thu, 29 Oct 2020 15:47:20 +1100 Subject: [PATCH 053/179] extmod/btstack/btstack.mk: Add -Wimplicit-fallthrough=0. This is needed since -Wextra was added to the build in bef412789ea93c521bd9c2dddc22b9b3484da574 Signed-off-by: Jim Mussared --- extmod/btstack/btstack.mk | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extmod/btstack/btstack.mk b/extmod/btstack/btstack.mk index 7e5d2f646d14c..e3309b61db7a4 100644 --- a/extmod/btstack/btstack.mk +++ b/extmod/btstack/btstack.mk @@ -66,11 +66,12 @@ endif LIB_SRC_C += $(SRC_BTSTACK) # Suppress some warnings. -BTSTACK_WARNING_CFLAGS = -Wno-old-style-definition -Wno-unused-variable -Wno-unused-parameter +BTSTACK_WARNING_CFLAGS = -Wno-old-style-definition -Wno-unused-variable -Wno-unused-parameter -Wimplicit-fallthrough=0 ifneq ($(CC),clang) BTSTACK_WARNING_CFLAGS += -Wno-format endif $(BUILD)/lib/btstack/src/%.o: CFLAGS += $(BTSTACK_WARNING_CFLAGS) +$(BUILD)/lib/btstack/platform/%.o: CFLAGS += $(BTSTACK_WARNING_CFLAGS) endif endif From b7883ce74c5a9b9689d812d134117d625fd42e73 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Wed, 4 Nov 2020 10:04:26 +1100 Subject: [PATCH 054/179] extmod/nimble/nimble.mk: Add -Wno-old-style-declaration. This is needed since -Wextra was added to the build in bef412789ea93c521bd9c2dddc22b9b3484da574 Signed-off-by: Jim Mussared --- extmod/nimble/nimble.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extmod/nimble/nimble.mk b/extmod/nimble/nimble.mk index f8a68bc6f52b6..fbd031b3e3e16 100644 --- a/extmod/nimble/nimble.mk +++ b/extmod/nimble/nimble.mk @@ -101,7 +101,7 @@ INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/include INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/transport/uart/include INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/porting/nimble/include -$(BUILD)/$(NIMBLE_LIB_DIR)/%.o: CFLAGS += -Wno-maybe-uninitialized -Wno-pointer-arith -Wno-unused-but-set-variable -Wno-format -Wno-sign-compare +$(BUILD)/$(NIMBLE_LIB_DIR)/%.o: CFLAGS += -Wno-maybe-uninitialized -Wno-pointer-arith -Wno-unused-but-set-variable -Wno-format -Wno-sign-compare -Wno-old-style-declaration endif From 1e297c88989592258965b69cb740039e26c7636c Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 2 Oct 2020 17:38:56 +1000 Subject: [PATCH 055/179] stm32/main: Move update_reset_mode to outside the soft-reset loop. Running the update inside the soft-reset loop will mean that (on boards like PYBD that use a bootloader) the same reset mode is used each reset loop, eg factory reset occurs each time. Signed-off-by: Damien George --- ports/stm32/main.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ports/stm32/main.c b/ports/stm32/main.c index f19dac0e7947b..7f8ee84fd4d6b 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -552,6 +552,11 @@ void stm32_main(uint32_t reset_mode) { MP_STATE_PORT(pyb_uart_obj_all)[MICROPY_HW_UART_REPL - 1] = &pyb_uart_repl_obj; #endif + #if !MICROPY_HW_USES_BOOTLOADER + // check if user switch held to select the reset mode + reset_mode = update_reset_mode(1); + #endif + soft_reset: #if defined(MICROPY_HW_LED2) @@ -564,11 +569,6 @@ void stm32_main(uint32_t reset_mode) { led_state(3, 0); led_state(4, 0); - #if !MICROPY_HW_USES_BOOTLOADER - // check if user switch held to select the reset mode - reset_mode = update_reset_mode(1); - #endif - // Python threading init #if MICROPY_PY_THREAD mp_thread_init(); @@ -776,5 +776,8 @@ void stm32_main(uint32_t reset_mode) { gc_sweep_all(); + // Set reset_mode to normal boot. + reset_mode = 1; + goto soft_reset; } From 4c3976bbcaf58266fdcaab264fee5b7a94a682e5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 28 Oct 2020 00:44:18 +1100 Subject: [PATCH 056/179] stm32: Add MICROPY_BOARD calls in various places in stm32_main. For a board to have full configurability of the soft reset loop. Signed-off-by: Damien George --- ports/stm32/Makefile | 1 + ports/stm32/boardctrl.c | 183 ++++++++++++++++++++++++++++++++++++++++ ports/stm32/boardctrl.h | 80 ++++++++++++++++++ ports/stm32/main.c | 164 ++++++++--------------------------- 4 files changed, 298 insertions(+), 130 deletions(-) create mode 100644 ports/stm32/boardctrl.c create mode 100644 ports/stm32/boardctrl.h diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 61da9cc9807aa..fa9998a8e078b 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -267,6 +267,7 @@ DRIVERS_SRC_C = $(addprefix drivers/,\ ) SRC_C += \ + boardctrl.c \ main.c \ stm32_it.c \ usbd_conf.c \ diff --git a/ports/stm32/boardctrl.c b/ports/stm32/boardctrl.c new file mode 100644 index 0000000000000..188068d705a2a --- /dev/null +++ b/ports/stm32/boardctrl.c @@ -0,0 +1,183 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/mphal.h" +#include "lib/utils/pyexec.h" +#include "boardctrl.h" +#include "led.h" +#include "usrsw.h" + +STATIC void flash_error(int n) { + for (int i = 0; i < n; i++) { + led_state(PYB_LED_RED, 1); + led_state(PYB_LED_GREEN, 0); + mp_hal_delay_ms(250); + led_state(PYB_LED_RED, 0); + led_state(PYB_LED_GREEN, 1); + mp_hal_delay_ms(250); + } + led_state(PYB_LED_GREEN, 0); +} + +#if !MICROPY_HW_USES_BOOTLOADER +STATIC uint update_reset_mode(uint reset_mode) { + #if MICROPY_HW_HAS_SWITCH + if (switch_get()) { + + // The original method used on the pyboard is appropriate if you have 2 + // or more LEDs. + #if defined(MICROPY_HW_LED2) + for (uint i = 0; i < 3000; i++) { + if (!switch_get()) { + break; + } + mp_hal_delay_ms(20); + if (i % 30 == 29) { + if (++reset_mode > 3) { + reset_mode = 1; + } + led_state(2, reset_mode & 1); + led_state(3, reset_mode & 2); + led_state(4, reset_mode & 4); + } + } + // flash the selected reset mode + for (uint i = 0; i < 6; i++) { + led_state(2, 0); + led_state(3, 0); + led_state(4, 0); + mp_hal_delay_ms(50); + led_state(2, reset_mode & 1); + led_state(3, reset_mode & 2); + led_state(4, reset_mode & 4); + mp_hal_delay_ms(50); + } + mp_hal_delay_ms(400); + + #elif defined(MICROPY_HW_LED1) + + // For boards with only a single LED, we'll flash that LED the + // appropriate number of times, with a pause between each one + for (uint i = 0; i < 10; i++) { + led_state(1, 0); + for (uint j = 0; j < reset_mode; j++) { + if (!switch_get()) { + break; + } + led_state(1, 1); + mp_hal_delay_ms(100); + led_state(1, 0); + mp_hal_delay_ms(200); + } + mp_hal_delay_ms(400); + if (!switch_get()) { + break; + } + if (++reset_mode > 3) { + reset_mode = 1; + } + } + // Flash the selected reset mode + for (uint i = 0; i < 2; i++) { + for (uint j = 0; j < reset_mode; j++) { + led_state(1, 1); + mp_hal_delay_ms(100); + led_state(1, 0); + mp_hal_delay_ms(200); + } + mp_hal_delay_ms(400); + } + #else + #error Need a reset mode update method + #endif + } + #endif + return reset_mode; +} +#endif + +void boardctrl_before_soft_reset_loop(boardctrl_state_t *state) { + #if !MICROPY_HW_USES_BOOTLOADER + // Update the reset_mode via the default + // method which uses the board switch/button and LEDs. + state->reset_mode = update_reset_mode(1); + #endif +} + +void boardctrl_top_soft_reset_loop(boardctrl_state_t *state) { + // Turn on a single LED to indicate start up. + #if defined(MICROPY_HW_LED2) + led_state(1, 0); + led_state(2, 1); + #else + led_state(1, 1); + led_state(2, 0); + #endif + led_state(3, 0); + led_state(4, 0); +} + +void boardctrl_before_boot_py(boardctrl_state_t *state) { + state->run_boot_py = state->reset_mode == 1 || state->reset_mode == 3; +} + +void boardctrl_after_boot_py(boardctrl_state_t *state) { + if (state->run_boot_py && !state->last_ret) { + flash_error(4); + } + + // Turn boot-up LEDs off + + #if !defined(MICROPY_HW_LED2) + // If there is only one LED on the board then it's used to signal boot-up + // and so we turn it off here. Otherwise LED(1) is used to indicate dirty + // flash cache and so we shouldn't change its state. + led_state(1, 0); + #endif + led_state(2, 0); + led_state(3, 0); + led_state(4, 0); +} + +void boardctrl_before_main_py(boardctrl_state_t *state) { + state->run_main_py = (state->reset_mode == 1 || state->reset_mode == 3) + && pyexec_mode_kind == PYEXEC_MODE_FRIENDLY_REPL; +} + +void boardctrl_after_main_py(boardctrl_state_t *state) { + if (state->run_main_py && !state->last_ret) { + flash_error(3); + } +} + +void boardctrl_start_soft_reset(boardctrl_state_t *state) { + state->log_soft_reset = true; +} + +void boardctrl_end_soft_reset(boardctrl_state_t *state) { + // Set reset_mode to normal boot. + state->reset_mode = 1; +} diff --git a/ports/stm32/boardctrl.h b/ports/stm32/boardctrl.h new file mode 100644 index 0000000000000..05bade305c254 --- /dev/null +++ b/ports/stm32/boardctrl.h @@ -0,0 +1,80 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_STM32_BOARDCTRL_H +#define MICROPY_INCLUDED_STM32_BOARDCTRL_H + +#include "py/mpconfig.h" + +#ifndef MICROPY_BOARD_BEFORE_SOFT_RESET_LOOP +#define MICROPY_BOARD_BEFORE_SOFT_RESET_LOOP boardctrl_before_soft_reset_loop +#endif + +#ifndef MICROPY_BOARD_TOP_SOFT_RESET_LOOP +#define MICROPY_BOARD_TOP_SOFT_RESET_LOOP boardctrl_top_soft_reset_loop +#endif + +#ifndef MICROPY_BOARD_BEFORE_BOOT_PY +#define MICROPY_BOARD_BEFORE_BOOT_PY boardctrl_before_boot_py +#endif + +#ifndef MICROPY_BOARD_AFTER_BOOT_PY +#define MICROPY_BOARD_AFTER_BOOT_PY boardctrl_after_boot_py +#endif + +#ifndef MICROPY_BOARD_BEFORE_MAIN_PY +#define MICROPY_BOARD_BEFORE_MAIN_PY boardctrl_before_main_py +#endif + +#ifndef MICROPY_BOARD_AFTER_MAIN_PY +#define MICROPY_BOARD_AFTER_MAIN_PY boardctrl_after_main_py +#endif + +#ifndef MICROPY_BOARD_START_SOFT_RESET +#define MICROPY_BOARD_START_SOFT_RESET boardctrl_start_soft_reset +#endif + +#ifndef MICROPY_BOARD_END_SOFT_RESET +#define MICROPY_BOARD_END_SOFT_RESET boardctrl_end_soft_reset +#endif + +typedef struct _boardctrl_state_t { + uint8_t reset_mode; + bool run_boot_py; + bool run_main_py; + bool log_soft_reset; + int last_ret; +} boardctrl_state_t; + +void boardctrl_before_soft_reset_loop(boardctrl_state_t *state); +void boardctrl_top_soft_reset_loop(boardctrl_state_t *state); +void boardctrl_before_boot_py(boardctrl_state_t *state); +void boardctrl_after_boot_py(boardctrl_state_t *state); +void boardctrl_before_main_py(boardctrl_state_t *state); +void boardctrl_after_main_py(boardctrl_state_t *state); +void boardctrl_start_soft_reset(boardctrl_state_t *state); +void boardctrl_end_soft_reset(boardctrl_state_t *state); + +#endif // MICROPY_INCLUDED_STM32_BOARDCTRL_H diff --git a/ports/stm32/main.c b/ports/stm32/main.c index 7f8ee84fd4d6b..c749cac978ba8 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2013-2018 Damien P. George + * Copyright (c) 2013-2020 Damien P. George * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -53,6 +53,7 @@ #include "extmod/modbluetooth.h" #endif +#include "boardctrl.h" #include "mpu.h" #include "rfcore.h" #include "systick.h" @@ -95,18 +96,6 @@ STATIC pyb_uart_obj_t pyb_uart_repl_obj; STATIC uint8_t pyb_uart_repl_rxbuf[MICROPY_HW_UART_REPL_RXBUF]; #endif -void flash_error(int n) { - for (int i = 0; i < n; i++) { - led_state(PYB_LED_RED, 1); - led_state(PYB_LED_GREEN, 0); - mp_hal_delay_ms(250); - led_state(PYB_LED_RED, 0); - led_state(PYB_LED_GREEN, 1); - mp_hal_delay_ms(250); - } - led_state(PYB_LED_GREEN, 0); -} - void NORETURN __fatal_error(const char *msg) { for (volatile uint delay = 0; delay < 10000000; delay++) { } @@ -310,83 +299,6 @@ STATIC bool init_sdcard_fs(void) { } #endif -#if !MICROPY_HW_USES_BOOTLOADER -STATIC uint update_reset_mode(uint reset_mode) { - #if MICROPY_HW_HAS_SWITCH - if (switch_get()) { - - // The original method used on the pyboard is appropriate if you have 2 - // or more LEDs. - #if defined(MICROPY_HW_LED2) - for (uint i = 0; i < 3000; i++) { - if (!switch_get()) { - break; - } - mp_hal_delay_ms(20); - if (i % 30 == 29) { - if (++reset_mode > 3) { - reset_mode = 1; - } - led_state(2, reset_mode & 1); - led_state(3, reset_mode & 2); - led_state(4, reset_mode & 4); - } - } - // flash the selected reset mode - for (uint i = 0; i < 6; i++) { - led_state(2, 0); - led_state(3, 0); - led_state(4, 0); - mp_hal_delay_ms(50); - led_state(2, reset_mode & 1); - led_state(3, reset_mode & 2); - led_state(4, reset_mode & 4); - mp_hal_delay_ms(50); - } - mp_hal_delay_ms(400); - - #elif defined(MICROPY_HW_LED1) - - // For boards with only a single LED, we'll flash that LED the - // appropriate number of times, with a pause between each one - for (uint i = 0; i < 10; i++) { - led_state(1, 0); - for (uint j = 0; j < reset_mode; j++) { - if (!switch_get()) { - break; - } - led_state(1, 1); - mp_hal_delay_ms(100); - led_state(1, 0); - mp_hal_delay_ms(200); - } - mp_hal_delay_ms(400); - if (!switch_get()) { - break; - } - if (++reset_mode > 3) { - reset_mode = 1; - } - } - // Flash the selected reset mode - for (uint i = 0; i < 2; i++) { - for (uint j = 0; j < reset_mode; j++) { - led_state(1, 1); - mp_hal_delay_ms(100); - led_state(1, 0); - mp_hal_delay_ms(200); - } - mp_hal_delay_ms(400); - } - #else - #error Need a reset mode update method - #endif - } - #endif - return reset_mode; -} -#endif - void stm32_main(uint32_t reset_mode) { #if !defined(STM32F0) && defined(MICROPY_HW_VTOR) // Change IRQ vector table if configured differently @@ -552,22 +464,17 @@ void stm32_main(uint32_t reset_mode) { MP_STATE_PORT(pyb_uart_obj_all)[MICROPY_HW_UART_REPL - 1] = &pyb_uart_repl_obj; #endif - #if !MICROPY_HW_USES_BOOTLOADER - // check if user switch held to select the reset mode - reset_mode = update_reset_mode(1); - #endif + boardctrl_state_t state; + state.reset_mode = reset_mode; + state.run_boot_py = false; + state.run_main_py = false; + state.last_ret = 0; + + MICROPY_BOARD_BEFORE_SOFT_RESET_LOOP(&state); soft_reset: - #if defined(MICROPY_HW_LED2) - led_state(1, 0); - led_state(2, 1); - #else - led_state(1, 1); - led_state(2, 0); - #endif - led_state(3, 0); - led_state(4, 0); + MICROPY_BOARD_TOP_SOFT_RESET_LOOP(&state); // Python threading init #if MICROPY_PY_THREAD @@ -656,29 +563,19 @@ void stm32_main(uint32_t reset_mode) { // reset config variables; they should be set by boot.py MP_STATE_PORT(pyb_config_main) = MP_OBJ_NULL; + MICROPY_BOARD_BEFORE_BOOT_PY(&state); + // run boot.py, if it exists // TODO perhaps have pyb.reboot([bootpy]) function to soft-reboot and execute custom boot.py - if (reset_mode == 1 || reset_mode == 3) { + if (state.run_boot_py) { const char *boot_py = "boot.py"; - int ret = pyexec_file_if_exists(boot_py); - if (ret & PYEXEC_FORCED_EXIT) { + state.last_ret = pyexec_file_if_exists(boot_py); + if (state.last_ret & PYEXEC_FORCED_EXIT) { goto soft_reset_exit; } - if (!ret) { - flash_error(4); - } } - // turn boot-up LEDs off - #if !defined(MICROPY_HW_LED2) - // If there is only one LED on the board then it's used to signal boot-up - // and so we turn it off here. Otherwise LED(1) is used to indicate dirty - // flash cache and so we shouldn't change its state. - led_state(1, 0); - #endif - led_state(2, 0); - led_state(3, 0); - led_state(4, 0); + MICROPY_BOARD_AFTER_BOOT_PY(&state); // Now we initialise sub-systems that need configuration from boot.py, // or whose initialisation can be safely deferred until after running @@ -713,23 +610,24 @@ void stm32_main(uint32_t reset_mode) { // At this point everything is fully configured and initialised. + MICROPY_BOARD_BEFORE_MAIN_PY(&state); + // Run the main script from the current directory. - if ((reset_mode == 1 || reset_mode == 3) && pyexec_mode_kind == PYEXEC_MODE_FRIENDLY_REPL) { + if (state.run_main_py) { const char *main_py; if (MP_STATE_PORT(pyb_config_main) == MP_OBJ_NULL) { main_py = "main.py"; } else { main_py = mp_obj_str_get_str(MP_STATE_PORT(pyb_config_main)); } - int ret = pyexec_file_if_exists(main_py); - if (ret & PYEXEC_FORCED_EXIT) { + state.last_ret = pyexec_file_if_exists(main_py); + if (state.last_ret & PYEXEC_FORCED_EXIT) { goto soft_reset_exit; } - if (!ret) { - flash_error(3); - } } + MICROPY_BOARD_AFTER_MAIN_PY(&state); + #if MICROPY_ENABLE_COMPILER // Main script is finished, so now go into REPL mode. // The REPL mode can change, or it can request a soft reset. @@ -750,12 +648,19 @@ void stm32_main(uint32_t reset_mode) { // soft reset + MICROPY_BOARD_START_SOFT_RESET(&state); + #if MICROPY_HW_ENABLE_STORAGE - printf("MPY: sync filesystems\n"); + if (state.log_soft_reset) { + mp_printf(&mp_plat_print, "MPY: sync filesystems\n"); + } storage_flush(); #endif - printf("MPY: soft reboot\n"); + if (state.log_soft_reset) { + mp_printf(&mp_plat_print, "MPY: soft reboot\n"); + } + #if MICROPY_PY_BLUETOOTH mp_bluetooth_deinit(); #endif @@ -774,10 +679,9 @@ void stm32_main(uint32_t reset_mode) { pyb_thread_deinit(); #endif - gc_sweep_all(); + MICROPY_BOARD_END_SOFT_RESET(&state); - // Set reset_mode to normal boot. - reset_mode = 1; + gc_sweep_all(); goto soft_reset; } From b99300b53ecc8db1ced8d96953ba4eaf2026f069 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 11 Nov 2020 18:14:50 +1100 Subject: [PATCH 057/179] stm32/boardctrl: Define MICROPY_BOARD_EARLY_INIT alongside others. Signed-off-by: Damien George --- ports/stm32/boardctrl.h | 4 ++++ ports/stm32/main.c | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ports/stm32/boardctrl.h b/ports/stm32/boardctrl.h index 05bade305c254..6f79dfb890707 100644 --- a/ports/stm32/boardctrl.h +++ b/ports/stm32/boardctrl.h @@ -28,6 +28,10 @@ #include "py/mpconfig.h" +#ifndef MICROPY_BOARD_EARLY_INIT +#define MICROPY_BOARD_EARLY_INIT() +#endif + #ifndef MICROPY_BOARD_BEFORE_SOFT_RESET_LOOP #define MICROPY_BOARD_BEFORE_SOFT_RESET_LOOP boardctrl_before_soft_reset_loop #endif diff --git a/ports/stm32/main.c b/ports/stm32/main.c index c749cac978ba8..db8222479b0f6 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -385,10 +385,7 @@ void stm32_main(uint32_t reset_mode) { __HAL_RCC_D2SRAM3_CLK_ENABLE(); #endif - - #if defined(MICROPY_BOARD_EARLY_INIT) MICROPY_BOARD_EARLY_INIT(); - #endif // basic sub-system init #if defined(STM32WB) From 7789cd5f16b068f032e58be8e5e60bf17cb03d40 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 31 Oct 2020 00:22:11 +1100 Subject: [PATCH 058/179] lib/utils/pyexec: Add MICROPY_BOARD hooks before/after executing code. Signed-off-by: Damien George --- lib/utils/pyexec.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/utils/pyexec.c b/lib/utils/pyexec.c index 2c8ca2de0cecc..2b86af3bbaadd 100644 --- a/lib/utils/pyexec.c +++ b/lib/utils/pyexec.c @@ -68,10 +68,15 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input uint32_t start = 0; #endif + #ifdef MICROPY_BOARD_BEFORE_PYTHON_EXEC + MICROPY_BOARD_BEFORE_PYTHON_EXEC(input_kind, exec_flags); + #endif + // by default a SystemExit exception returns 0 pyexec_system_exit = 0; nlr_buf_t nlr; + nlr.ret_val = NULL; if (nlr_push(&nlr) == 0) { mp_obj_t module_fun; #if MICROPY_MODULE_FROZEN_MPY @@ -157,6 +162,10 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input mp_hal_stdout_tx_strn("\x04", 1); } + #ifdef MICROPY_BOARD_AFTER_PYTHON_EXEC + MICROPY_BOARD_AFTER_PYTHON_EXEC(input_kind, exec_flags, nlr.ret_val, &ret); + #endif + return ret; } From 1fef5662ab96a27c2b279082607103bbe5da9de5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 5 Nov 2020 22:35:02 +1100 Subject: [PATCH 059/179] py/mpz: Do sign extension in mpz_as_bytes for negative values. Signed-off-by: Damien George --- py/mpz.c | 10 +++++++++- py/objint_mpz.c | 1 - tests/basics/struct1_intbig.py | 8 ++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/py/mpz.c b/py/mpz.c index b3d88067929fd..75159305382a1 100644 --- a/py/mpz.c +++ b/py/mpz.c @@ -1609,7 +1609,6 @@ bool mpz_as_uint_checked(const mpz_t *i, mp_uint_t *value) { return true; } -// writes at most len bytes to buf (so buf should be zeroed before calling) void mpz_as_bytes(const mpz_t *z, bool big_endian, size_t len, byte *buf) { byte *b = buf; if (big_endian) { @@ -1641,6 +1640,15 @@ void mpz_as_bytes(const mpz_t *z, bool big_endian, size_t len, byte *buf) { } } } + + // fill remainder of buf with zero/sign extension of the integer + if (big_endian) { + len = b - buf; + } else { + len = buf + len - b; + buf = b; + } + memset(buf, z->neg ? 0xff : 0x00, len); } #if MICROPY_PY_BUILTINS_FLOAT diff --git a/py/objint_mpz.c b/py/objint_mpz.c index 6e52073a6e85b..ef3e017967857 100644 --- a/py/objint_mpz.c +++ b/py/objint_mpz.c @@ -116,7 +116,6 @@ mp_obj_t mp_obj_int_from_bytes_impl(bool big_endian, size_t len, const byte *buf void mp_obj_int_to_bytes_impl(mp_obj_t self_in, bool big_endian, size_t len, byte *buf) { assert(mp_obj_is_type(self_in, &mp_type_int)); mp_obj_int_t *self = MP_OBJ_TO_PTR(self_in); - memset(buf, 0, len); mpz_as_bytes(&self->mpz, big_endian, len, buf); } diff --git a/tests/basics/struct1_intbig.py b/tests/basics/struct1_intbig.py index 380293f36cea7..24541c8a42509 100644 --- a/tests/basics/struct1_intbig.py +++ b/tests/basics/struct1_intbig.py @@ -36,3 +36,11 @@ # check small int overflow print(struct.unpack("": + for type_ in "bhiq": + fmt = endian + type_ + b = struct.pack(fmt, -2 + bigzero) + print(fmt, b, struct.unpack(fmt, b)) From bdfb584b294bf1379921b08ec020386b8ff6257b Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 5 Nov 2020 22:39:54 +1100 Subject: [PATCH 060/179] extmod/moductypes: Fix storing to (U)INT64 arrays on 32-bit archs. Fixes issue #6583. Signed-off-by: Damien George --- extmod/moductypes.c | 2 +- tests/extmod/uctypes_array_load_store.py | 19 ++++++++++++++++ tests/extmod/uctypes_array_load_store.py.exp | 24 ++++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 tests/extmod/uctypes_array_load_store.py create mode 100644 tests/extmod/uctypes_array_load_store.py.exp diff --git a/extmod/moductypes.c b/extmod/moductypes.c index c5fbf12e42b9d..79a49d5c329c2 100644 --- a/extmod/moductypes.c +++ b/extmod/moductypes.c @@ -399,7 +399,7 @@ STATIC void set_aligned(uint val_type, void *p, mp_int_t index, mp_obj_t val) { ((uint64_t *)p)[index] = (uint64_t)v; } else { // TODO: Doesn't offer atomic store semantics, but should at least try - set_unaligned(val_type, p, MP_ENDIANNESS_BIG, val); + set_unaligned(val_type, (void *)&((uint64_t *)p)[index], MP_ENDIANNESS_BIG, val); } return; default: diff --git a/tests/extmod/uctypes_array_load_store.py b/tests/extmod/uctypes_array_load_store.py new file mode 100644 index 0000000000000..709b9f5c29b18 --- /dev/null +++ b/tests/extmod/uctypes_array_load_store.py @@ -0,0 +1,19 @@ +# Test uctypes array, load and store, with array size > 1 + +try: + import uctypes +except ImportError: + print("SKIP") + raise SystemExit + +N = 3 + +for endian in ("NATIVE", "LITTLE_ENDIAN", "BIG_ENDIAN"): + for type_ in ("INT8", "UINT8", "INT16", "UINT16", "INT32", "UINT32", "INT64", "UINT64"): + desc = {"arr": (uctypes.ARRAY | 0, getattr(uctypes, type_) | N)} + sz = uctypes.sizeof(desc) + data = bytearray(sz) + s = uctypes.struct(uctypes.addressof(data), desc, getattr(uctypes, endian)) + for i in range(N): + s.arr[i] = i + print(endian, type_, sz, *(s.arr[i] for i in range(N))) diff --git a/tests/extmod/uctypes_array_load_store.py.exp b/tests/extmod/uctypes_array_load_store.py.exp new file mode 100644 index 0000000000000..f6f7bc96ef1a0 --- /dev/null +++ b/tests/extmod/uctypes_array_load_store.py.exp @@ -0,0 +1,24 @@ +NATIVE INT8 3 0 1 2 +NATIVE UINT8 3 0 1 2 +NATIVE INT16 6 0 1 2 +NATIVE UINT16 6 0 1 2 +NATIVE INT32 12 0 1 2 +NATIVE UINT32 12 0 1 2 +NATIVE INT64 24 0 1 2 +NATIVE UINT64 24 0 1 2 +LITTLE_ENDIAN INT8 3 0 1 2 +LITTLE_ENDIAN UINT8 3 0 1 2 +LITTLE_ENDIAN INT16 6 0 1 2 +LITTLE_ENDIAN UINT16 6 0 1 2 +LITTLE_ENDIAN INT32 12 0 1 2 +LITTLE_ENDIAN UINT32 12 0 1 2 +LITTLE_ENDIAN INT64 24 0 1 2 +LITTLE_ENDIAN UINT64 24 0 1 2 +BIG_ENDIAN INT8 3 0 1 2 +BIG_ENDIAN UINT8 3 0 1 2 +BIG_ENDIAN INT16 6 0 1 2 +BIG_ENDIAN UINT16 6 0 1 2 +BIG_ENDIAN INT32 12 0 1 2 +BIG_ENDIAN UINT32 12 0 1 2 +BIG_ENDIAN INT64 24 0 1 2 +BIG_ENDIAN UINT64 24 0 1 2 From d7e1526593151b33ab52af445647c6d1315a96dc Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 5 Nov 2020 22:42:28 +1100 Subject: [PATCH 061/179] py/binary: Fix sign extension setting wide integer on 32-bit archs. Signed-off-by: Damien George --- py/binary.c | 2 +- tests/extmod/uctypes_array_load_store.py | 4 +- tests/extmod/uctypes_array_load_store.py.exp | 48 ++++++++++---------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/py/binary.c b/py/binary.c index d0f72ec23c49c..1847894b71068 100644 --- a/py/binary.c +++ b/py/binary.c @@ -343,7 +343,7 @@ void mp_binary_set_val(char struct_type, char val_type, mp_obj_t val_in, byte *p val = mp_obj_get_int(val_in); // zero/sign extend if needed if (BYTES_PER_WORD < 8 && size > sizeof(val)) { - int c = (is_signed(val_type) && (mp_int_t)val < 0) ? 0xff : 0x00; + int c = (mp_int_t)val < 0 ? 0xff : 0x00; memset(p, c, size); if (struct_type == '>') { p += size - sizeof(val); diff --git a/tests/extmod/uctypes_array_load_store.py b/tests/extmod/uctypes_array_load_store.py index 709b9f5c29b18..3b9bb6d7308ca 100644 --- a/tests/extmod/uctypes_array_load_store.py +++ b/tests/extmod/uctypes_array_load_store.py @@ -6,7 +6,7 @@ print("SKIP") raise SystemExit -N = 3 +N = 5 for endian in ("NATIVE", "LITTLE_ENDIAN", "BIG_ENDIAN"): for type_ in ("INT8", "UINT8", "INT16", "UINT16", "INT32", "UINT32", "INT64", "UINT64"): @@ -15,5 +15,5 @@ data = bytearray(sz) s = uctypes.struct(uctypes.addressof(data), desc, getattr(uctypes, endian)) for i in range(N): - s.arr[i] = i + s.arr[i] = i - 2 print(endian, type_, sz, *(s.arr[i] for i in range(N))) diff --git a/tests/extmod/uctypes_array_load_store.py.exp b/tests/extmod/uctypes_array_load_store.py.exp index f6f7bc96ef1a0..10de8046454ba 100644 --- a/tests/extmod/uctypes_array_load_store.py.exp +++ b/tests/extmod/uctypes_array_load_store.py.exp @@ -1,24 +1,24 @@ -NATIVE INT8 3 0 1 2 -NATIVE UINT8 3 0 1 2 -NATIVE INT16 6 0 1 2 -NATIVE UINT16 6 0 1 2 -NATIVE INT32 12 0 1 2 -NATIVE UINT32 12 0 1 2 -NATIVE INT64 24 0 1 2 -NATIVE UINT64 24 0 1 2 -LITTLE_ENDIAN INT8 3 0 1 2 -LITTLE_ENDIAN UINT8 3 0 1 2 -LITTLE_ENDIAN INT16 6 0 1 2 -LITTLE_ENDIAN UINT16 6 0 1 2 -LITTLE_ENDIAN INT32 12 0 1 2 -LITTLE_ENDIAN UINT32 12 0 1 2 -LITTLE_ENDIAN INT64 24 0 1 2 -LITTLE_ENDIAN UINT64 24 0 1 2 -BIG_ENDIAN INT8 3 0 1 2 -BIG_ENDIAN UINT8 3 0 1 2 -BIG_ENDIAN INT16 6 0 1 2 -BIG_ENDIAN UINT16 6 0 1 2 -BIG_ENDIAN INT32 12 0 1 2 -BIG_ENDIAN UINT32 12 0 1 2 -BIG_ENDIAN INT64 24 0 1 2 -BIG_ENDIAN UINT64 24 0 1 2 +NATIVE INT8 5 -2 -1 0 1 2 +NATIVE UINT8 5 254 255 0 1 2 +NATIVE INT16 10 -2 -1 0 1 2 +NATIVE UINT16 10 65534 65535 0 1 2 +NATIVE INT32 20 -2 -1 0 1 2 +NATIVE UINT32 20 4294967294 4294967295 0 1 2 +NATIVE INT64 40 -2 -1 0 1 2 +NATIVE UINT64 40 18446744073709551614 18446744073709551615 0 1 2 +LITTLE_ENDIAN INT8 5 -2 -1 0 1 2 +LITTLE_ENDIAN UINT8 5 254 255 0 1 2 +LITTLE_ENDIAN INT16 10 -2 -1 0 1 2 +LITTLE_ENDIAN UINT16 10 65534 65535 0 1 2 +LITTLE_ENDIAN INT32 20 -2 -1 0 1 2 +LITTLE_ENDIAN UINT32 20 4294967294 4294967295 0 1 2 +LITTLE_ENDIAN INT64 40 -2 -1 0 1 2 +LITTLE_ENDIAN UINT64 40 18446744073709551614 18446744073709551615 0 1 2 +BIG_ENDIAN INT8 5 -2 -1 0 1 2 +BIG_ENDIAN UINT8 5 254 255 0 1 2 +BIG_ENDIAN INT16 10 -2 -1 0 1 2 +BIG_ENDIAN UINT16 10 65534 65535 0 1 2 +BIG_ENDIAN INT32 20 -2 -1 0 1 2 +BIG_ENDIAN UINT32 20 4294967294 4294967295 0 1 2 +BIG_ENDIAN INT64 40 -2 -1 0 1 2 +BIG_ENDIAN UINT64 40 18446744073709551614 18446744073709551615 0 1 2 From a7932ae4e66bd1c354eee357f18393bd83144144 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Thu, 29 Oct 2020 16:38:13 +1100 Subject: [PATCH 062/179] tools/makeqstrdefs.py: Run qstr preprocessing in parallel. This gives a substantial speedup of the preprocessing step, i.e. the generation of qstr.i.last. For example on a clean build, making qstr.i.last: 21s -> 4s on STM32 (WB55) 8.9 -> 1.8s on Unix (dev). Done in collaboration with @stinos. Signed-off-by: Jim Mussared --- py/makeqstrdefs.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/py/makeqstrdefs.py b/py/makeqstrdefs.py index f514ae0c1052f..ad4f22d5e4cf3 100644 --- a/py/makeqstrdefs.py +++ b/py/makeqstrdefs.py @@ -7,11 +7,12 @@ from __future__ import print_function +import io +import os import re import subprocess import sys -import io -import os +import multiprocessing, multiprocessing.dummy # Extract MP_QSTR_FOO macros. @@ -39,11 +40,27 @@ def preprocess(): os.makedirs(os.path.dirname(args.output[0])) except OSError: pass - with open(args.output[0], "w") as out_file: - if csources: - subprocess.check_call(args.pp + args.cflags + csources, stdout=out_file) - if cxxsources: - subprocess.check_call(args.pp + args.cxxflags + cxxsources, stdout=out_file) + + def pp(flags): + def run(files): + return subprocess.check_output(args.pp + flags + files) + + return run + + try: + cpus = multiprocessing.cpu_count() + except NotImplementedError: + cpus = 1 + p = multiprocessing.dummy.Pool(cpus) + with open(args.output[0], "wb") as out_file: + for flags, sources in ( + (args.cflags, csources), + (args.cxxflags, cxxsources), + ): + batch_size = (len(sources) + cpus - 1) // cpus + chunks = [sources[i : i + batch_size] for i in range(0, len(sources), batch_size or 1)] + for output in p.imap(pp(flags), chunks): + out_file.write(output) def write_out(fname, output): From b04240cb77c5305a63b4ca7b36229d410eca2072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20NEDJAR?= Date: Wed, 4 Nov 2020 14:26:56 +0200 Subject: [PATCH 063/179] stm32/Makefile: Make the generation of `firmware.bin` explicit. The file `$(BUILD)/firmware.bin` was used by the target `deploy-stlink` and `deploy-openocd` but it was generated indirectly by the target `firmware.dfu`. As this file could be used to program boards directly by a Mass Storage copy, it's better to make it explicitly generated. Additionally, some target are refactored to remove redundancy and be more explicit on dependencies. --- ports/stm32/Makefile | 112 +++++++++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 41 deletions(-) diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index fa9998a8e078b..8f7cdd93ca5fe 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -564,15 +564,56 @@ CFLAGS += -DMICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool CFLAGS += -DMICROPY_MODULE_FROZEN_MPY endif -.PHONY: deploy +define RUN_DFU + $(ECHO) "Writing $(1) to the board" + $(if $(filter $(USE_PYDFU),1),\ + $(Q)$(PYTHON) $(PYDFU) --vid $(BOOTLOADER_DFU_USB_VID) --pid $(BOOTLOADER_DFU_USB_PID) -u $(1), + $(Q)$(DFU_UTIL) -a 0 -d $(BOOTLOADER_DFU_USB_VID):$(BOOTLOADER_DFU_USB_PID) -D $(1)) +endef + +define RUN_STLINK + $(ECHO) "Writing $(1) to the board via ST-LINK" + $(Q)$(STFLASH) write $(1) $(2) +endef + +define RUN_OPENOCD + $(ECHO) "Writing $(1) to the board via ST-LINK using OpenOCD" + $(Q)$(OPENOCD) -f $(OPENOCD_CONFIG) -c "stm_flash $(1) $(2) $(3) $(4)" +endef + +define GENERATE_ELF + $(ECHO) "LINK $(1)" + $(Q)$(LD) $(LDFLAGS) -o $(1) $(2) $(LDFLAGS_MOD) $(LIBS) + $(Q)$(SIZE) $(1) + $(if $(filter-out $(TEXT0_ADDR),0x08000000), \ + $(ECHO) "INFO: this build requires mboot to be installed first") + $(if $(filter $(TEXT1_ADDR),0x90000000), \ + $(ECHO) "INFO: this build places firmware in external QSPI flash") +endef + +define GENERATE_BIN + $(ECHO) "GEN $(1)" + $(Q)$(OBJCOPY) -O binary $(addprefix -j ,$(3)) $(2) $(1) +endef + +define GENERATE_DFU + $(ECHO) "GEN $(1)" + $(Q)$(PYTHON) $(DFU) \ + -D $(BOOTLOADER_DFU_USB_VID):$(BOOTLOADER_DFU_USB_PID) \ + $(if $(2),$(addprefix -b ,$(3):$(2))) \ + $(if $(4),$(addprefix -b ,$(5):$(4))) \ + $(1) +endef + +define GENERATE_HEX + $(ECHO) "GEN $(1)" + $(Q)$(OBJCOPY) -O ihex $(2) $(1) +endef + +.PHONY: deploy deploy-stlink deploy-openocd deploy: $(BUILD)/firmware.dfu - $(ECHO) "Writing $< to the board" -ifeq ($(USE_PYDFU),1) - $(Q)$(PYTHON) $(PYDFU) --vid $(BOOTLOADER_DFU_USB_VID) --pid $(BOOTLOADER_DFU_USB_PID) -u $< -else - $(Q)$(DFU_UTIL) -a 0 -d $(BOOTLOADER_DFU_USB_VID):$(BOOTLOADER_DFU_USB_PID) -D $< -endif + $(call RUN_DFU,$^) # A board should specify TEXT0_ADDR if to use a different location than the # default for the firmware memory location. A board can also optionally define @@ -584,18 +625,17 @@ ifeq ($(TEXT1_ADDR),) TEXT0_SECTIONS ?= .isr_vector .text .data -deploy-stlink: $(BUILD)/firmware.dfu - $(ECHO) "Writing $(BUILD)/firmware.bin to the board via ST-LINK" - $(Q)$(STFLASH) write $(BUILD)/firmware.bin $(TEXT0_ADDR) +deploy-stlink: $(BUILD)/firmware.bin + $(call RUN_STLINK,$^,$(TEXT0_ADDR)) -deploy-openocd: $(BUILD)/firmware.dfu - $(ECHO) "Writing $(BUILD)/firmware.bin to the board via ST-LINK using OpenOCD" - $(Q)$(OPENOCD) -f $(OPENOCD_CONFIG) -c "stm_flash $(BUILD)/firmware.bin $(TEXT0_ADDR)" +deploy-openocd: $(BUILD)/firmware.bin + $(call RUN_OPENOCD,$^,$(TEXT0_ADDR)) -$(BUILD)/firmware.dfu: $(BUILD)/firmware.elf - $(ECHO) "Create $@" - $(Q)$(OBJCOPY) -O binary $(addprefix -j ,$(TEXT0_SECTIONS)) $^ $(BUILD)/firmware.bin - $(Q)$(PYTHON) $(DFU) -D $(BOOTLOADER_DFU_USB_VID):$(BOOTLOADER_DFU_USB_PID) -b $(TEXT0_ADDR):$(BUILD)/firmware.bin $@ +$(BUILD)/firmware.bin: $(BUILD)/firmware.elf + $(call GENERATE_BIN,$@,$^,$(TEXT0_SECTIONS)) + +$(BUILD)/firmware.dfu: $(BUILD)/firmware.bin + $(call GENERATE_DFU,$@,$^,$(TEXT0_ADDR)) else # TEXT0_ADDR and TEXT1_ADDR are specified so split firmware between these locations @@ -603,38 +643,28 @@ else TEXT0_SECTIONS ?= .isr_vector TEXT1_SECTIONS ?= .text .data -deploy-stlink: $(BUILD)/firmware.dfu - $(ECHO) "Writing $(BUILD)/firmware0.bin to the board via ST-LINK" - $(Q)$(STFLASH) write $(BUILD)/firmware0.bin $(TEXT0_ADDR) - $(ECHO) "Writing $(BUILD)/firmware1.bin to the board via ST-LINK" - $(Q)$(STFLASH) --reset write $(BUILD)/firmware1.bin $(TEXT1_ADDR) +deploy-stlink: $(BUILD)/firmware0.bin $(BUILD)/firmware1.bin + $(call RUN_STLINK,$(word 1,$^),$(TEXT0_ADDR)) + $(call RUN_STLINK,$(word 2,$^),$(TEXT1_ADDR)) -deploy-openocd: $(BUILD)/firmware.dfu - $(ECHO) "Writing $(BUILD)/firmware{0,1}.bin to the board via ST-LINK using OpenOCD" - $(Q)$(OPENOCD) -f $(OPENOCD_CONFIG) -c "stm_flash $(BUILD)/firmware0.bin $(TEXT0_ADDR) $(BUILD)/firmware1.bin $(TEXT1_ADDR)" +deploy-openocd: $(BUILD)/firmware0.bin $(BUILD)/firmware1.bin + $(call RUN_OPENOCD,$(word 1,$^),$(TEXT0_ADDR),$(word 2,$^),$(TEXT1_ADDR)) -$(BUILD)/firmware.dfu: $(BUILD)/firmware.elf - $(ECHO) "GEN $@" - $(Q)$(OBJCOPY) -O binary $(addprefix -j ,$(TEXT0_SECTIONS)) $^ $(BUILD)/firmware0.bin - $(Q)$(OBJCOPY) -O binary $(addprefix -j ,$(TEXT1_SECTIONS)) $^ $(BUILD)/firmware1.bin - $(Q)$(PYTHON) $(DFU) -D $(BOOTLOADER_DFU_USB_VID):$(BOOTLOADER_DFU_USB_PID) -b $(TEXT0_ADDR):$(BUILD)/firmware0.bin -b $(TEXT1_ADDR):$(BUILD)/firmware1.bin $@ +$(BUILD)/firmware0.bin: $(BUILD)/firmware.elf + $(call GENERATE_BIN,$@,$^,$(TEXT0_SECTIONS)) +$(BUILD)/firmware1.bin: $(BUILD)/firmware.elf + $(call GENERATE_BIN,$@,$^,$(TEXT1_SECTIONS)) + +$(BUILD)/firmware.dfu: $(BUILD)/firmware0.bin $(BUILD)/firmware1.bin + $(call GENERATE_DFU,$@,$(word 1,$^),$(TEXT0_ADDR),$(word 2,$^),$(TEXT1_ADDR)) endif $(BUILD)/firmware.hex: $(BUILD)/firmware.elf - $(ECHO) "GEN $@" - $(Q)$(OBJCOPY) -O ihex $< $@ + $(call GENERATE_HEX,$@,$^) $(BUILD)/firmware.elf: $(OBJ) - $(ECHO) "LINK $@" - $(Q)$(LD) $(LDFLAGS) -o $@ $^ $(LDFLAGS_MOD) $(LIBS) - $(Q)$(SIZE) $@ -ifneq ($(TEXT0_ADDR),0x08000000) - $(ECHO) "INFO: this build requires mboot to be installed first" -endif -ifeq ($(TEXT1_ADDR),0x90000000) - $(ECHO) "INFO: this build places firmware in external QSPI flash" -endif + $(call GENERATE_ELF,$@,$^) PLLVALUES = boards/pllvalues.py MAKE_PINS = boards/make-pins.py From 8a917ad2529ea3df5f47e2be5b4edf362d2d03f6 Mon Sep 17 00:00:00 2001 From: Jonathan Hogg Date: Mon, 9 Nov 2020 13:11:52 +0000 Subject: [PATCH 064/179] esp32/machine_pin: Reset pin if init sets mode. This will forcibly grab the pin back from the ADC if it has previously been associated with it. Fixes #5771. --- ports/esp32/machine_pin.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/esp32/machine_pin.c b/ports/esp32/machine_pin.c index a55cc1f4abaca..dcdd53be92378 100644 --- a/ports/esp32/machine_pin.c +++ b/ports/esp32/machine_pin.c @@ -150,6 +150,11 @@ STATIC mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_ mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + // reset the pin first if this is a mode-setting init (grab it back from ADC) + if (args[ARG_mode].u_obj != mp_const_none) { + gpio_reset_pin(self->id); + } + // configure the pin for gpio gpio_pad_select_gpio(self->id); From 922f81dfd1b94e23f23f05aac0ecafac8645ce46 Mon Sep 17 00:00:00 2001 From: Arrowana Date: Sat, 31 Oct 2020 13:34:58 +1100 Subject: [PATCH 065/179] extmod/machine_mem: Only allow integers in machine.memX subscript. Prior to this change machine.mem32['foo'] (or using any other non-integer subscript) could result in a fault due to 'foo' being interpreted as an integer. And when writing code it's hard to tell if the fault is due to a bad subscript type, or an integer subscript that specifies an invalid memory address. The type of the object used in the subscript is now tested to be an integer by using mp_obj_get_int_truncated instead of mp_obj_int_get_truncated. The performance hit of this change is minimal, and machine.memX objects are more for convenience than performance (there are many other ways to read/write memory in a faster way), Fixes issue #6588. --- extmod/machine_mem.c | 2 +- ports/unix/modmachine.c | 2 +- tests/extmod/machine1.py | 20 ++++++++++++++++++++ tests/extmod/machine1.py.exp | 4 ++++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/extmod/machine_mem.c b/extmod/machine_mem.c index a1494bd51ad17..73e2f7fd1fcb0 100644 --- a/extmod/machine_mem.c +++ b/extmod/machine_mem.c @@ -40,7 +40,7 @@ #if !defined(MICROPY_MACHINE_MEM_GET_READ_ADDR) || !defined(MICROPY_MACHINE_MEM_GET_WRITE_ADDR) STATIC uintptr_t machine_mem_get_addr(mp_obj_t addr_o, uint align) { - uintptr_t addr = mp_obj_int_get_truncated(addr_o); + uintptr_t addr = mp_obj_get_int_truncated(addr_o); if ((addr & (align - 1)) != 0) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("address %08x is not aligned to %d bytes"), addr, align); } diff --git a/ports/unix/modmachine.c b/ports/unix/modmachine.c index a6eb8c01edd05..5b462a3b1534b 100644 --- a/ports/unix/modmachine.c +++ b/ports/unix/modmachine.c @@ -47,7 +47,7 @@ #if MICROPY_PY_MACHINE uintptr_t mod_machine_mem_get_addr(mp_obj_t addr_o, uint align) { - uintptr_t addr = mp_obj_int_get_truncated(addr_o); + uintptr_t addr = mp_obj_get_int_truncated(addr_o); if ((addr & (align - 1)) != 0) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("address %08x is not aligned to %d bytes"), addr, align); } diff --git a/tests/extmod/machine1.py b/tests/extmod/machine1.py index 6ff38cc05179d..0c7f8122f4ae4 100644 --- a/tests/extmod/machine1.py +++ b/tests/extmod/machine1.py @@ -26,3 +26,23 @@ del machine.mem8[0] except TypeError: print("TypeError") + +try: + machine.mem8[0:1] +except TypeError: + print("TypeError") + +try: + machine.mem8[0:1] = 10 +except TypeError: + print("TypeError") + +try: + machine.mem8["hello"] +except TypeError: + print("TypeError") + +try: + machine.mem8["hello"] = 10 +except TypeError: + print("TypeError") diff --git a/tests/extmod/machine1.py.exp b/tests/extmod/machine1.py.exp index bb421ea5cfa42..250485969086e 100644 --- a/tests/extmod/machine1.py.exp +++ b/tests/extmod/machine1.py.exp @@ -2,3 +2,7 @@ ValueError ValueError TypeError +TypeError +TypeError +TypeError +TypeError From a0623a081c0dde50aa8d87b464232f2d7866f951 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 12 Nov 2020 12:04:56 +1100 Subject: [PATCH 066/179] stm32/Makefile: Allow boards to extend all SRC variables. And rename SRC_HAL -> HAL_SRC_C and SRC_USBDEV -> USBDEV_SRC_C for consistency with other source variables. Follow on from 0fff2e03fe07471997a6df6f92c6960cfd225dc0 Signed-off-by: Damien George --- ports/stm32/Makefile | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 8f7cdd93ca5fe..18f7e8a1a10d8 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -148,7 +148,7 @@ endif # Options for mpy-cross MPY_CROSS_FLAGS += -march=armv7m -LIB_SRC_C = $(addprefix lib/,\ +LIB_SRC_C += $(addprefix lib/,\ libc/string0.c \ mp-readline/readline.c \ netutils/netutils.c \ @@ -163,7 +163,7 @@ LIB_SRC_C = $(addprefix lib/,\ ) ifeq ($(MICROPY_FLOAT_IMPL),double) -LIBM_SRC_C = $(addprefix lib/libm_dbl/,\ +LIBM_SRC_C += $(addprefix lib/libm_dbl/,\ __cos.c \ __expo2.c \ __fpclassify.c \ @@ -213,7 +213,7 @@ else LIBM_SRC_C += lib/libm_dbl/sqrt.c endif else -LIBM_SRC_C = $(addprefix lib/libm/,\ +LIBM_SRC_C += $(addprefix lib/libm/,\ math.c \ acoshf.c \ asinfacosf.c \ @@ -255,11 +255,11 @@ ifeq ($(MICROPY_FLOAT_IMPL),double) $(LIBM_O): CFLAGS := $(filter-out -Wdouble-promotion -Wfloat-conversion, $(CFLAGS)) endif -EXTMOD_SRC_C = $(addprefix extmod/,\ +EXTMOD_SRC_C += $(addprefix extmod/,\ modonewire.c \ ) -DRIVERS_SRC_C = $(addprefix drivers/,\ +DRIVERS_SRC_C += $(addprefix drivers/,\ bus/softspi.c \ bus/softqspi.c \ memory/spiflash.c \ @@ -363,7 +363,7 @@ SRC_O += \ endif endif -SRC_HAL = $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_,\ +HAL_SRC_C += $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_,\ hal.c \ hal_adc.c \ hal_adc_ex.c \ @@ -388,13 +388,13 @@ SRC_HAL = $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_,\ ) ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f4 f7 h7 l0 l4 wb)) -SRC_HAL += $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_,\ +HAL_SRC_C += $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_,\ ll_usb.c \ ) endif ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f4 f7 h7 l4)) -SRC_HAL += $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_,\ +HAL_SRC_C += $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_,\ hal_sd.c \ ll_sdmmc.c \ ll_fmc.c \ @@ -402,7 +402,7 @@ SRC_HAL += $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_,\ endif ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f4 f7 h7)) -SRC_HAL += $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_,\ +HAL_SRC_C += $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_,\ hal_mmc.c \ hal_sdram.c \ hal_dma_ex.c \ @@ -411,14 +411,14 @@ SRC_HAL += $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_,\ endif ifeq ($(CMSIS_MCU),$(filter $(CMSIS_MCU),STM32H743xx)) - SRC_HAL += $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_, hal_fdcan.c) + HAL_SRC_C += $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_, hal_fdcan.c) else ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f0 f4 f7 h7 l4)) - SRC_HAL += $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_, hal_can.c) + HAL_SRC_C += $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_, hal_can.c) endif endif -SRC_USBDEV = $(addprefix $(USBDEV_DIR)/,\ +USBDEV_SRC_C += $(addprefix $(USBDEV_DIR)/,\ core/src/usbd_core.c \ core/src/usbd_ctlreq.c \ core/src/usbd_ioreq.c \ @@ -521,11 +521,11 @@ OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o)) OBJ += $(LIBM_O) OBJ += $(addprefix $(BUILD)/, $(EXTMOD_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(DRIVERS_SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(HAL_SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(USBDEV_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_CXX:.cpp=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_O)) -OBJ += $(addprefix $(BUILD)/, $(SRC_HAL:.c=.o)) -OBJ += $(addprefix $(BUILD)/, $(SRC_USBDEV:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_MOD:.c=.o)) OBJ += $(BUILD)/pins_$(BOARD).o From cc2a35b7b241e7eda031db424bf9b3afb8b6204b Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 12 Nov 2020 14:35:43 +1100 Subject: [PATCH 067/179] stm32/rtc: Validate the RTC prescaler on boot and change if incorrect. Devices with RTC backup-batteries have been shown (very rarely) to have incorrect RTC prescaler values. Such incorrect values mean the RTC counts fast or slow, and will be wrong forever if the power/backup-battery is always present. This commit detects such a state at start up (hard reset) and corrects it by reconfiguring the RTC prescaler values. Signed-off-by: Damien George --- ports/stm32/boards/stm32f0xx_hal_conf_base.h | 1 + ports/stm32/boards/stm32f4xx_hal_conf_base.h | 1 + ports/stm32/boards/stm32f7xx_hal_conf_base.h | 1 + ports/stm32/boards/stm32h7xx_hal_conf_base.h | 1 + ports/stm32/boards/stm32l0xx_hal_conf_base.h | 1 + ports/stm32/boards/stm32l4xx_hal_conf_base.h | 1 + ports/stm32/boards/stm32wbxx_hal_conf_base.h | 1 + ports/stm32/rtc.c | 40 ++++++++++++++++++-- 8 files changed, 44 insertions(+), 3 deletions(-) diff --git a/ports/stm32/boards/stm32f0xx_hal_conf_base.h b/ports/stm32/boards/stm32f0xx_hal_conf_base.h index faceda2f4fa90..5c6f31d1dd81b 100644 --- a/ports/stm32/boards/stm32f0xx_hal_conf_base.h +++ b/ports/stm32/boards/stm32f0xx_hal_conf_base.h @@ -48,6 +48,7 @@ #include "stm32f0xx_hal_usart.h" #include "stm32f0xx_hal_wwdg.h" #include "stm32f0xx_ll_adc.h" +#include "stm32f0xx_ll_rtc.h" // Enable various HAL modules #define HAL_MODULE_ENABLED diff --git a/ports/stm32/boards/stm32f4xx_hal_conf_base.h b/ports/stm32/boards/stm32f4xx_hal_conf_base.h index cdae0c5629e20..8d8bb8f4eeccc 100644 --- a/ports/stm32/boards/stm32f4xx_hal_conf_base.h +++ b/ports/stm32/boards/stm32f4xx_hal_conf_base.h @@ -53,6 +53,7 @@ #include "stm32f4xx_hal_uart.h" #include "stm32f4xx_hal_usart.h" #include "stm32f4xx_hal_wwdg.h" +#include "stm32f4xx_ll_rtc.h" // Enable various HAL modules #define HAL_ADC_MODULE_ENABLED diff --git a/ports/stm32/boards/stm32f7xx_hal_conf_base.h b/ports/stm32/boards/stm32f7xx_hal_conf_base.h index 05ab10fea08ad..83a144f8fe7f0 100644 --- a/ports/stm32/boards/stm32f7xx_hal_conf_base.h +++ b/ports/stm32/boards/stm32f7xx_hal_conf_base.h @@ -53,6 +53,7 @@ #include "stm32f7xx_hal_uart.h" #include "stm32f7xx_hal_usart.h" #include "stm32f7xx_hal_wwdg.h" +#include "stm32f7xx_ll_rtc.h" // Enable various HAL modules #define HAL_ADC_MODULE_ENABLED diff --git a/ports/stm32/boards/stm32h7xx_hal_conf_base.h b/ports/stm32/boards/stm32h7xx_hal_conf_base.h index 9f43da40308f7..231f1ac7f4d28 100644 --- a/ports/stm32/boards/stm32h7xx_hal_conf_base.h +++ b/ports/stm32/boards/stm32h7xx_hal_conf_base.h @@ -54,6 +54,7 @@ #include "stm32h7xx_hal_usart.h" #include "stm32h7xx_hal_wwdg.h" #include "stm32h7xx_ll_adc.h" +#include "stm32h7xx_ll_rtc.h" // Enable various HAL modules #define HAL_ADC_MODULE_ENABLED diff --git a/ports/stm32/boards/stm32l0xx_hal_conf_base.h b/ports/stm32/boards/stm32l0xx_hal_conf_base.h index b100daaa98eb3..6b5ece766a83c 100644 --- a/ports/stm32/boards/stm32l0xx_hal_conf_base.h +++ b/ports/stm32/boards/stm32l0xx_hal_conf_base.h @@ -47,6 +47,7 @@ #include "stm32l0xx_hal_usart.h" #include "stm32l0xx_hal_wwdg.h" #include "stm32l0xx_ll_adc.h" +#include "stm32l0xx_ll_rtc.h" // Enable various HAL modules #define HAL_MODULE_ENABLED diff --git a/ports/stm32/boards/stm32l4xx_hal_conf_base.h b/ports/stm32/boards/stm32l4xx_hal_conf_base.h index cfffcffbbe134..215e798b92f2e 100644 --- a/ports/stm32/boards/stm32l4xx_hal_conf_base.h +++ b/ports/stm32/boards/stm32l4xx_hal_conf_base.h @@ -51,6 +51,7 @@ #include "stm32l4xx_hal_usart.h" #include "stm32l4xx_hal_wwdg.h" #include "stm32l4xx_ll_adc.h" +#include "stm32l4xx_ll_rtc.h" // Enable various HAL modules #define HAL_MODULE_ENABLED diff --git a/ports/stm32/boards/stm32wbxx_hal_conf_base.h b/ports/stm32/boards/stm32wbxx_hal_conf_base.h index 83d07ad5b1c3b..91309e286fa1e 100644 --- a/ports/stm32/boards/stm32wbxx_hal_conf_base.h +++ b/ports/stm32/boards/stm32wbxx_hal_conf_base.h @@ -42,6 +42,7 @@ #include "stm32wbxx_hal_uart.h" #include "stm32wbxx_hal_usart.h" #include "stm32wbxx_ll_adc.h" +#include "stm32wbxx_ll_rtc.h" // Enable various HAL modules #define HAL_MODULE_ENABLED diff --git a/ports/stm32/rtc.c b/ports/stm32/rtc.c index 18ecf7750513b..bd898d4558570 100644 --- a/ports/stm32/rtc.c +++ b/ports/stm32/rtc.c @@ -114,20 +114,22 @@ void rtc_init_start(bool force_init) { rtc_need_init_finalise = false; if (!force_init) { + bool rtc_running = false; uint32_t bdcr = RCC->BDCR; if ((bdcr & (RCC_BDCR_RTCEN | RCC_BDCR_RTCSEL | RCC_BDCR_LSEON | RCC_BDCR_LSERDY)) == (RCC_BDCR_RTCEN | RCC_BDCR_RTCSEL_0 | RCC_BDCR_LSEON | RCC_BDCR_LSERDY)) { // LSE is enabled & ready --> no need to (re-)init RTC + rtc_running = true; // remove Backup Domain write protection HAL_PWR_EnableBkUpAccess(); // Clear source Reset Flag __HAL_RCC_CLEAR_RESET_FLAGS(); // provide some status information - rtc_info |= 0x40000 | (RCC->BDCR & 7) | (RCC->CSR & 3) << 8; - return; + rtc_info |= 0x40000; } else if ((bdcr & (RCC_BDCR_RTCEN | RCC_BDCR_RTCSEL)) == (RCC_BDCR_RTCEN | RCC_BDCR_RTCSEL_1)) { // LSI configured as the RTC clock source --> no need to (re-)init RTC + rtc_running = true; // remove Backup Domain write protection HAL_PWR_EnableBkUpAccess(); // Clear source Reset Flag @@ -135,7 +137,39 @@ void rtc_init_start(bool force_init) { // Turn the LSI on (it may need this even if the RTC is running) RCC->CSR |= RCC_CSR_LSION; // provide some status information - rtc_info |= 0x80000 | (RCC->BDCR & 7) | (RCC->CSR & 3) << 8; + rtc_info |= 0x80000; + } + + if (rtc_running) { + // Provide information about the registers that indicated the RTC is running. + rtc_info |= (RCC->BDCR & 7) | (RCC->CSR & 3) << 8; + + // Check that the sync and async prescaler values are correct. If the RTC + // gets into a state where they are wrong then it will run slow or fast and + // never be corrected. In such a situation, attempt to reconfigure the values + // without changing the data/time. + if (LL_RTC_GetSynchPrescaler(RTC) != RTC_SYNCH_PREDIV + || LL_RTC_GetAsynchPrescaler(RTC) != RTC_ASYNCH_PREDIV) { + // Values are wrong, attempt to enter RTC init mode and change them. + LL_RTC_DisableWriteProtection(RTC); + LL_RTC_EnableInitMode(RTC); + uint32_t ticks_ms = HAL_GetTick(); + while (HAL_GetTick() - ticks_ms < RTC_TIMEOUT_VALUE) { + if (LL_RTC_IsActiveFlag_INIT(RTC)) { + // Reconfigure the RTC prescaler register PRER. + LL_RTC_SetSynchPrescaler(RTC, RTC_SYNCH_PREDIV); + LL_RTC_SetAsynchPrescaler(RTC, RTC_ASYNCH_PREDIV); + LL_RTC_DisableInitMode(RTC); + break; + } + } + LL_RTC_EnableWriteProtection(RTC); + + // Provide information that the prescaler was changed. + rtc_info |= 0x100000; + } + + // The RTC is up and running, so return without any further configuration. return; } } From 309fb822e6f24de54dbd107d4573a3bedac0bf9e Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 3 Nov 2020 17:38:09 +1100 Subject: [PATCH 068/179] tests/run-multitests.py: Fix diff order, show changes relative to truth. Signed-off-by: Jim Mussared --- tests/run-multitests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/run-multitests.py b/tests/run-multitests.py index ad032df38b238..5b8e70e921a7a 100755 --- a/tests/run-multitests.py +++ b/tests/run-multitests.py @@ -389,7 +389,7 @@ def run_tests(test_files, instances_truth, instances_test): print("### TRUTH ###") print(output_truth, end="") print("### DIFF ###") - print_diff(output_test, output_truth) + print_diff(output_truth, output_test) if cmd_args.show_output: print() From ccfd535af48f2ffc00f7306648b60624894d3004 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 3 Nov 2020 17:41:28 +1100 Subject: [PATCH 069/179] tests/multi_bluetooth: Improve reliability of event waiting. Use the same `wait_for_event` in all tests that doesn't hold a reference to the event data tuple and handles repeat events. Also fix a few misc reliability issues around timeouts and sequencing. Signed-off-by: Jim Mussared --- tests/multi_bluetooth/ble_characteristic.py | 91 ++++++++----------- .../multi_bluetooth/ble_characteristic.py.exp | 1 + tests/multi_bluetooth/ble_gap_advertise.py | 5 +- tests/multi_bluetooth/ble_gap_connect.py | 70 +++++--------- tests/multi_bluetooth/ble_gap_device_name.py | 53 +++++------ .../ble_gap_device_name.py.exp | 1 + .../multi_bluetooth/ble_gatt_data_transfer.py | 62 +++++-------- .../ble_gattc_discover_services.py | 37 ++++---- .../ble_gattc_discover_services.py.exp | 2 + tests/multi_bluetooth/ble_mtu.py | 54 +++++------ 10 files changed, 155 insertions(+), 221 deletions(-) diff --git a/tests/multi_bluetooth/ble_characteristic.py b/tests/multi_bluetooth/ble_characteristic.py index 026b9d551c2b9..0f22daff88f0a 100644 --- a/tests/multi_bluetooth/ble_characteristic.py +++ b/tests/multi_bluetooth/ble_characteristic.py @@ -31,28 +31,31 @@ ) SERVICES = (SERVICE,) -waiting_event = None -waiting_data = None -value_handle = 0 +waiting_events = {} def irq(event, data): - global waiting_event, waiting_data, value_handle if event == _IRQ_CENTRAL_CONNECT: print("_IRQ_CENTRAL_CONNECT") + waiting_events[event] = data[0] elif event == _IRQ_CENTRAL_DISCONNECT: print("_IRQ_CENTRAL_DISCONNECT") elif event == _IRQ_GATTS_WRITE: print("_IRQ_GATTS_WRITE", ble.gatts_read(data[-1])) elif event == _IRQ_PERIPHERAL_CONNECT: print("_IRQ_PERIPHERAL_CONNECT") + waiting_events[event] = data[0] elif event == _IRQ_PERIPHERAL_DISCONNECT: print("_IRQ_PERIPHERAL_DISCONNECT") elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT: # conn_handle, def_handle, value_handle, properties, uuid = data if data[-1] == CHAR_UUID: print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) - value_handle = data[2] + waiting_events[event] = data[2] + else: + return + elif event == _IRQ_GATTC_CHARACTERISTIC_DONE: + print("_IRQ_GATTC_CHARACTERISTIC_DONE") elif event == _IRQ_GATTC_READ_RESULT: print("_IRQ_GATTC_READ_RESULT", bytes(data[-1])) elif event == _IRQ_GATTC_READ_DONE: @@ -66,25 +69,19 @@ def irq(event, data): elif event == _IRQ_GATTS_INDICATE_DONE: print("_IRQ_GATTS_INDICATE_DONE", data[-1]) - if waiting_event is not None: - if (isinstance(waiting_event, int) and event == waiting_event) or ( - not isinstance(waiting_event, int) and waiting_event(event, data) - ): - waiting_event = None - waiting_data = data + if event not in waiting_events: + waiting_events[event] = None def wait_for_event(event, timeout_ms): - global waiting_event, waiting_data - waiting_event = event - waiting_data = None - t0 = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: - if waiting_data: - return True + if event in waiting_events: + result = waiting_events[event] + del waiting_events[event] + return result machine.idle() - return False + raise ValueError("Timeout waiting for {}".format(event)) # Acting in peripheral role. @@ -99,39 +96,35 @@ def instance0(): ble.gatts_write(char_handle, "periph0") # Wait for central to connect to us. - if not wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS): - return - conn_handle, _, _ = waiting_data + conn_handle = wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) - # Wait for a write to the characteristic from the central. - wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS) + # A - # Wait a bit, then write the characteristic and notify it. - time.sleep_ms(1000) + # Wait for a write to the characteristic from the central, + # then reply with a notification. + wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS) print("gatts_write") ble.gatts_write(char_handle, "periph1") print("gatts_notify") ble.gatts_notify(conn_handle, char_handle) - # Wait for a write to the characteristic from the central. - wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS) + # B - # Wait a bit, then notify a new value on the characteristic. - time.sleep_ms(1000) + # Wait for a write to the characteristic from the central, + # then reply with value-included notification. + wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS) print("gatts_notify") ble.gatts_notify(conn_handle, char_handle, "periph2") - # Wait for a write to the characteristic from the central. - wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS) + # C - # Wait a bit, then notify a new value on the characteristic. - time.sleep_ms(1000) + # Wait for a write to the characteristic from the central, + # then reply with an indication. + wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS) print("gatts_write") ble.gatts_write(char_handle, "periph3") print("gatts_indicate") ble.gatts_indicate(conn_handle, char_handle) - - # Wait for the indicate ack. wait_for_event(_IRQ_GATTS_INDICATE_DONE, TIMEOUT_MS) # Wait for the central to disconnect. @@ -147,49 +140,45 @@ def instance1(): # Connect to peripheral and then disconnect. print("gap_connect") ble.gap_connect(*BDADDR) - if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): - return - conn_handle, _, _ = waiting_data + conn_handle = wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) # Discover characteristics. ble.gattc_discover_characteristics(conn_handle, 1, 65535) - wait_for_event(lambda event, data: value_handle, TIMEOUT_MS) + value_handle = wait_for_event(_IRQ_GATTC_CHARACTERISTIC_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_CHARACTERISTIC_DONE, TIMEOUT_MS) # Issue read of characteristic, should get initial value. print("gattc_read") ble.gattc_read(conn_handle, value_handle) wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) - # Write to the characteristic, and ask for a response. + # Write to the characteristic, which will trigger a notification. print("gattc_write") ble.gattc_write(conn_handle, value_handle, "central0", 1) wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS) - - # Wait for a notify, then read new value. + # A wait_for_event(_IRQ_GATTC_NOTIFY, TIMEOUT_MS) - print("gattc_read") + print("gattc_read") # Read the new value set immediately before notification. ble.gattc_read(conn_handle, value_handle) wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) - # Write to the characteristic, and ask for a response. + # Write to the characteristic, which will trigger a value-included notification. print("gattc_write") ble.gattc_write(conn_handle, value_handle, "central1", 1) wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS) - - # Wait for a notify (should have new data), then read old value (should be unchanged). + # B wait_for_event(_IRQ_GATTC_NOTIFY, TIMEOUT_MS) - print("gattc_read") + print("gattc_read") # Read value should be unchanged. ble.gattc_read(conn_handle, value_handle) wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) - # Write to the characteristic, and ask for a response. + # Write to the characteristic, which will trigger an indication. print("gattc_write") ble.gattc_write(conn_handle, value_handle, "central2", 1) wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS) - - # Wait for a indicate (should have new data), then read new value. + # C wait_for_event(_IRQ_GATTC_INDICATE, TIMEOUT_MS) - print("gattc_read") + print("gattc_read") # Read the new value set immediately before indication. ble.gattc_read(conn_handle, value_handle) wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) diff --git a/tests/multi_bluetooth/ble_characteristic.py.exp b/tests/multi_bluetooth/ble_characteristic.py.exp index 90dc46c06fd57..31667415eebf8 100644 --- a/tests/multi_bluetooth/ble_characteristic.py.exp +++ b/tests/multi_bluetooth/ble_characteristic.py.exp @@ -15,6 +15,7 @@ _IRQ_CENTRAL_DISCONNECT gap_connect _IRQ_PERIPHERAL_CONNECT _IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE gattc_read _IRQ_GATTC_READ_RESULT b'periph0' _IRQ_GATTC_READ_DONE 0 diff --git a/tests/multi_bluetooth/ble_gap_advertise.py b/tests/multi_bluetooth/ble_gap_advertise.py index bb6ff8e425461..bb1ef94a71e7b 100644 --- a/tests/multi_bluetooth/ble_gap_advertise.py +++ b/tests/multi_bluetooth/ble_gap_advertise.py @@ -46,7 +46,10 @@ def irq(ev, data): elif ev == _IRQ_SCAN_DONE: finished = True - ble.config(rxbuf=2000) + try: + ble.config(rxbuf=2000) + except: + pass ble.irq(irq) ble.gap_scan(2 * ADV_TIME_S * 1000, 10000, 10000) while not finished: diff --git a/tests/multi_bluetooth/ble_gap_connect.py b/tests/multi_bluetooth/ble_gap_connect.py index 8b40a29163119..2c1d2cbbc55dc 100644 --- a/tests/multi_bluetooth/ble_gap_connect.py +++ b/tests/multi_bluetooth/ble_gap_connect.py @@ -10,101 +10,77 @@ _IRQ_PERIPHERAL_CONNECT = const(7) _IRQ_PERIPHERAL_DISCONNECT = const(8) -central_connected = False -central_disconnected = False -peripheral_connected = False -peripheral_disconnected = False -conn_handle = None +waiting_events = {} def irq(event, data): - global central_connected, central_disconnected, peripheral_connected, peripheral_disconnected, conn_handle if event == _IRQ_CENTRAL_CONNECT: print("_IRQ_CENTRAL_CONNECT") - central_connected = True - conn_handle = data[0] + waiting_events[event] = data[0] elif event == _IRQ_CENTRAL_DISCONNECT: print("_IRQ_CENTRAL_DISCONNECT") - central_disconnected = True elif event == _IRQ_PERIPHERAL_CONNECT: print("_IRQ_PERIPHERAL_CONNECT") - peripheral_connected = True - conn_handle = data[0] + waiting_events[event] = data[0] elif event == _IRQ_PERIPHERAL_DISCONNECT: print("_IRQ_PERIPHERAL_DISCONNECT") - peripheral_disconnected = True - remote_addr = data[0] + if event not in waiting_events: + waiting_events[event] = None -def wait_for(fn, timeout_ms): + +def wait_for_event(event, timeout_ms): t0 = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: - if fn(): - return True + if event in waiting_events: + result = waiting_events[event] + del waiting_events[event] + return result machine.idle() - return False + raise ValueError("Timeout waiting for {}".format(event)) # Acting in peripheral role. def instance0(): - global central_connected, central_disconnected - multitest.globals(BDADDR=ble.config("mac")) print("gap_advertise") ble.gap_advertise(20_000, b"\x02\x01\x06\x04\xffMPY") multitest.next() try: # Wait for central to connect, then wait for it to disconnect. - if not wait_for(lambda: central_connected, TIMEOUT_MS): - return - if not wait_for(lambda: central_disconnected, TIMEOUT_MS): - return - - central_connected = False - central_disconnected = False + wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) + wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS) # Start advertising again. print("gap_advertise") ble.gap_advertise(20_000, b"\x02\x01\x06\x04\xffMPY") # Wait for central to connect, then disconnect it. - if not wait_for(lambda: central_connected, TIMEOUT_MS): - return + conn_handle = wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) print("gap_disconnect:", ble.gap_disconnect(conn_handle)) - if not wait_for(lambda: central_disconnected, TIMEOUT_MS): - return + wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS) finally: ble.active(0) # Acting in central role. def instance1(): - global peripheral_connected, peripheral_disconnected - multitest.next() try: # Connect to peripheral and then disconnect. print("gap_connect") ble.gap_connect(*BDADDR) - if not wait_for(lambda: peripheral_connected, TIMEOUT_MS): - return + conn_handle = wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) print("gap_disconnect:", ble.gap_disconnect(conn_handle)) - if not wait_for(lambda: peripheral_disconnected, TIMEOUT_MS): - return - - peripheral_connected = False - peripheral_disconnected = False - - # Wait for peripheral to start advertising again. - time.sleep_ms(100) + wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS) # Connect to peripheral and then let the peripheral disconnect us. + # Extra scan timeout allows for the peripheral to receive the disconnect + # event and start advertising again. print("gap_connect") - ble.gap_connect(*BDADDR) - if not wait_for(lambda: peripheral_connected, TIMEOUT_MS): - return - if not wait_for(lambda: peripheral_disconnected, TIMEOUT_MS): - return + ble.gap_connect(BDADDR[0], BDADDR[1], 5000) + wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) + wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS) finally: ble.active(0) diff --git a/tests/multi_bluetooth/ble_gap_device_name.py b/tests/multi_bluetooth/ble_gap_device_name.py index fbc9d80baeb08..e7202170bd8a4 100644 --- a/tests/multi_bluetooth/ble_gap_device_name.py +++ b/tests/multi_bluetooth/ble_gap_device_name.py @@ -16,45 +16,44 @@ GAP_DEVICE_NAME_UUID = bluetooth.UUID(0x2A00) -waiting_event = None -waiting_data = None -value_handle = 0 +waiting_events = {} def irq(event, data): - global waiting_event, waiting_data, value_handle if event == _IRQ_CENTRAL_CONNECT: print("_IRQ_CENTRAL_CONNECT") + waiting_events[event] = data[0] elif event == _IRQ_CENTRAL_DISCONNECT: print("_IRQ_CENTRAL_DISCONNECT") elif event == _IRQ_PERIPHERAL_CONNECT: print("_IRQ_PERIPHERAL_CONNECT") + waiting_events[event] = data[0] elif event == _IRQ_PERIPHERAL_DISCONNECT: print("_IRQ_PERIPHERAL_DISCONNECT") elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT: if data[-1] == GAP_DEVICE_NAME_UUID: print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) - value_handle = data[2] + waiting_events[event] = data[2] + else: + return + elif event == _IRQ_GATTC_CHARACTERISTIC_DONE: + print("_IRQ_GATTC_CHARACTERISTIC_DONE") elif event == _IRQ_GATTC_READ_RESULT: print("_IRQ_GATTC_READ_RESULT", bytes(data[-1])) - if waiting_event is not None: - if isinstance(waiting_event, int) and event == waiting_event: - waiting_event = None - waiting_data = data + if event not in waiting_events: + waiting_events[event] = None def wait_for_event(event, timeout_ms): - global waiting_event, waiting_data - waiting_event = event - waiting_data = None - t0 = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: - if waiting_data: - return True + if event in waiting_events: + result = waiting_events[event] + del waiting_events[event] + return result machine.idle() - return False + raise ValueError("Timeout waiting for {}".format(event)) # Acting in peripheral role. @@ -79,10 +78,8 @@ def instance0(): ble.gap_advertise(20_000) # Wait for central to connect, then wait for it to disconnect. - if not wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS): - return - if not wait_for_event(_IRQ_CENTRAL_DISCONNECT, 4 * TIMEOUT_MS): - return + wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) + wait_for_event(_IRQ_CENTRAL_DISCONNECT, 4 * TIMEOUT_MS) finally: ble.active(0) @@ -91,6 +88,7 @@ def instance0(): def instance1(): multitest.next() try: + value_handle = None for iteration in range(2): # Wait for peripheral to start advertising. time.sleep_ms(500) @@ -98,17 +96,15 @@ def instance1(): # Connect to peripheral. print("gap_connect") ble.gap_connect(*BDADDR) - if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): - return - conn_handle, _, _ = waiting_data + conn_handle = wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) if iteration == 0: + # Only do characteristic discovery on the first iteration, + # assume value_handle is unchanged on the second. print("gattc_discover_characteristics") ble.gattc_discover_characteristics(conn_handle, 1, 65535) - wait_for_event(lambda: value_handle, TIMEOUT_MS) - - # Wait for all characteristic results to come in (there should be an event for it). - time.sleep_ms(500) + value_handle = wait_for_event(_IRQ_GATTC_CHARACTERISTIC_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_CHARACTERISTIC_DONE, TIMEOUT_MS) # Read the peripheral's GAP device name. print("gattc_read") @@ -117,8 +113,7 @@ def instance1(): # Disconnect from peripheral. print("gap_disconnect:", ble.gap_disconnect(conn_handle)) - if not wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS): - return + wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS) finally: ble.active(0) diff --git a/tests/multi_bluetooth/ble_gap_device_name.py.exp b/tests/multi_bluetooth/ble_gap_device_name.py.exp index 0f4ee6e3dc576..a7a7aa367016a 100644 --- a/tests/multi_bluetooth/ble_gap_device_name.py.exp +++ b/tests/multi_bluetooth/ble_gap_device_name.py.exp @@ -12,6 +12,7 @@ gap_connect _IRQ_PERIPHERAL_CONNECT gattc_discover_characteristics _IRQ_GATTC_CHARACTERISTIC_RESULT UUID(0x2a00) +_IRQ_GATTC_CHARACTERISTIC_DONE gattc_read _IRQ_GATTC_READ_RESULT b'GAP_NAME0' gap_disconnect: True diff --git a/tests/multi_bluetooth/ble_gatt_data_transfer.py b/tests/multi_bluetooth/ble_gatt_data_transfer.py index 944c9e2d2a55f..e8249b3da8962 100644 --- a/tests/multi_bluetooth/ble_gatt_data_transfer.py +++ b/tests/multi_bluetooth/ble_gatt_data_transfer.py @@ -29,35 +29,33 @@ SERVICE = (SERVICE_UUID, (CHAR_CTRL, CHAR_RX, CHAR_TX)) SERVICES = (SERVICE,) -waiting_event = None -waiting_data = None -ctrl_value_handle = 0 -rx_value_handle = 0 -tx_value_handle = 0 +waiting_events = {} def irq(event, data): - global waiting_event, waiting_data, ctrl_value_handle, rx_value_handle, tx_value_handle if event == _IRQ_CENTRAL_CONNECT: print("_IRQ_CENTRAL_CONNECT") + waiting_events[event] = data[0] elif event == _IRQ_CENTRAL_DISCONNECT: print("_IRQ_CENTRAL_DISCONNECT") elif event == _IRQ_GATTS_WRITE: print("_IRQ_GATTS_WRITE") + waiting_events[(event, data[1])] = None elif event == _IRQ_PERIPHERAL_CONNECT: print("_IRQ_PERIPHERAL_CONNECT") + waiting_events[event] = data[0] elif event == _IRQ_PERIPHERAL_DISCONNECT: print("_IRQ_PERIPHERAL_DISCONNECT") elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT: if data[-1] == CHAR_CTRL_UUID: print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) - ctrl_value_handle = data[2] + waiting_events[(event, CHAR_CTRL_UUID)] = data[2] elif data[-1] == CHAR_RX_UUID: print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) - rx_value_handle = data[2] + waiting_events[(event, CHAR_RX_UUID)] = data[2] elif data[-1] == CHAR_TX_UUID: print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) - tx_value_handle = data[2] + waiting_events[(event, CHAR_TX_UUID)] = data[2] elif event == _IRQ_GATTC_CHARACTERISTIC_DONE: print("_IRQ_GATTC_CHARACTERISTIC_DONE") elif event == _IRQ_GATTC_READ_RESULT: @@ -68,26 +66,21 @@ def irq(event, data): print("_IRQ_GATTC_WRITE_DONE", data[-1]) elif event == _IRQ_GATTC_NOTIFY: print("_IRQ_GATTC_NOTIFY", bytes(data[-1])) + waiting_events[(event, data[1])] = None - if waiting_event is not None: - if (isinstance(waiting_event, int) and event == waiting_event) or ( - not isinstance(waiting_event, int) and waiting_event(event, data) - ): - waiting_event = None - waiting_data = data + if event not in waiting_events: + waiting_events[event] = None def wait_for_event(event, timeout_ms): - global waiting_event, waiting_data - waiting_event = event - waiting_data = None - t0 = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: - if waiting_data: - return True + if event in waiting_events: + result = waiting_events[event] + del waiting_events[event] + return result machine.idle() - return False + raise ValueError("Timeout waiting for {}".format(event)) # Acting in peripheral role. @@ -103,15 +96,10 @@ def instance0(): multitest.next() try: # Wait for central to connect to us. - if not wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS): - return - conn_handle, _, _ = waiting_data + conn_handle = wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) # Wait for the central to signal that it's done with its part of the test. - wait_for_event( - lambda event, data: event == _IRQ_GATTS_WRITE and data[1] == char_ctrl_handle, - 2 * TIMEOUT_MS, - ) + wait_for_event((_IRQ_GATTS_WRITE, char_ctrl_handle), 2 * TIMEOUT_MS) # Read all accumulated data from the central. print("gatts_read:", ble.gatts_read(char_rx_handle)) @@ -138,17 +126,14 @@ def instance1(): # Connect to peripheral and then disconnect. print("gap_connect") ble.gap_connect(*BDADDR) - if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): - return - conn_handle, _, _ = waiting_data + conn_handle = wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) # Discover characteristics. ble.gattc_discover_characteristics(conn_handle, 1, 65535) - wait_for_event( - lambda event, data: ctrl_value_handle and rx_value_handle and tx_value_handle, - TIMEOUT_MS, - ) wait_for_event(_IRQ_GATTC_CHARACTERISTIC_DONE, TIMEOUT_MS) + ctrl_value_handle = waiting_events[(_IRQ_GATTC_CHARACTERISTIC_RESULT, CHAR_CTRL_UUID)] + rx_value_handle = waiting_events[(_IRQ_GATTC_CHARACTERISTIC_RESULT, CHAR_RX_UUID)] + tx_value_handle = waiting_events[(_IRQ_GATTC_CHARACTERISTIC_RESULT, CHAR_TX_UUID)] # Write to the characteristic a few times, with and without response. for i in range(4): @@ -163,10 +148,7 @@ def instance1(): wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS) # Wait for notification that peripheral is done with its part of the test. - wait_for_event( - lambda event, data: event == _IRQ_GATTC_NOTIFY and data[1] == ctrl_value_handle, - TIMEOUT_MS, - ) + wait_for_event((_IRQ_GATTC_NOTIFY, ctrl_value_handle), TIMEOUT_MS) # Disconnect from peripheral. print("gap_disconnect:", ble.gap_disconnect(conn_handle)) diff --git a/tests/multi_bluetooth/ble_gattc_discover_services.py b/tests/multi_bluetooth/ble_gattc_discover_services.py index 57f95bf0125e7..00bb94bba0ba8 100644 --- a/tests/multi_bluetooth/ble_gattc_discover_services.py +++ b/tests/multi_bluetooth/ble_gattc_discover_services.py @@ -24,45 +24,42 @@ ) SERVICES = (SERVICE_A, SERVICE_B) -waiting_event = None -waiting_data = None +waiting_events = {} num_service_result = 0 def irq(event, data): - global waiting_event, waiting_data, num_service_result + global num_service_result if event == _IRQ_CENTRAL_CONNECT: print("_IRQ_CENTRAL_CONNECT") + waiting_events[event] = data[0] elif event == _IRQ_CENTRAL_DISCONNECT: print("_IRQ_CENTRAL_DISCONNECT") elif event == _IRQ_PERIPHERAL_CONNECT: print("_IRQ_PERIPHERAL_CONNECT") + waiting_events[event] = data[0] elif event == _IRQ_PERIPHERAL_DISCONNECT: print("_IRQ_PERIPHERAL_DISCONNECT") elif event == _IRQ_GATTC_SERVICE_RESULT: if data[3] == UUID_A or data[3] == UUID_B: print("_IRQ_GATTC_SERVICE_RESULT", data[3]) num_service_result += 1 + elif event == _IRQ_GATTC_SERVICE_DONE: + print("_IRQ_GATTC_SERVICE_DONE") - if waiting_event is not None: - if (isinstance(waiting_event, int) and event == waiting_event) or ( - not isinstance(waiting_event, int) and waiting_event(event, data) - ): - waiting_event = None - waiting_data = data + if event not in waiting_events: + waiting_events[event] = None def wait_for_event(event, timeout_ms): - global waiting_event, waiting_data - waiting_event = event - waiting_data = None - t0 = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: - if waiting_data: - return True + if event in waiting_events: + result = waiting_events[event] + del waiting_events[event] + return result machine.idle() - return False + raise ValueError("Timeout waiting for {}".format(event)) # Acting in peripheral role. @@ -86,13 +83,13 @@ def instance1(): # Connect to peripheral and then disconnect. print("gap_connect") ble.gap_connect(*BDADDR) - if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): - return - conn_handle, _, _ = waiting_data + conn_handle = wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) # Discover services. ble.gattc_discover_services(conn_handle) - wait_for_event(lambda event, data: num_service_result == 2, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_SERVICE_DONE, TIMEOUT_MS) + + print("discovered:", num_service_result) # Disconnect from peripheral. print("gap_disconnect:", ble.gap_disconnect(conn_handle)) diff --git a/tests/multi_bluetooth/ble_gattc_discover_services.py.exp b/tests/multi_bluetooth/ble_gattc_discover_services.py.exp index 7f4d8da81ad8c..d14f802377ca1 100644 --- a/tests/multi_bluetooth/ble_gattc_discover_services.py.exp +++ b/tests/multi_bluetooth/ble_gattc_discover_services.py.exp @@ -7,5 +7,7 @@ gap_connect _IRQ_PERIPHERAL_CONNECT _IRQ_GATTC_SERVICE_RESULT UUID(0x180d) _IRQ_GATTC_SERVICE_RESULT UUID('a5a5a5a5-ffff-9999-1111-5a5a5a5a5a5a') +_IRQ_GATTC_SERVICE_DONE +discovered: 2 gap_disconnect: True _IRQ_PERIPHERAL_DISCONNECT diff --git a/tests/multi_bluetooth/ble_mtu.py b/tests/multi_bluetooth/ble_mtu.py index ef3199c5bbb04..73c77e2460c65 100644 --- a/tests/multi_bluetooth/ble_mtu.py +++ b/tests/multi_bluetooth/ble_mtu.py @@ -51,7 +51,6 @@ def irq(event, data): - global value_handle if event == _IRQ_CENTRAL_CONNECT: print("_IRQ_CENTRAL_CONNECT") waiting_events[event] = data[0] @@ -68,6 +67,8 @@ def irq(event, data): if data[-1] == CHAR_UUID: print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) waiting_events[event] = data[2] + else: + return elif event == _IRQ_GATTC_CHARACTERISTIC_DONE: print("_IRQ_GATTC_CHARACTERISTIC_DONE") elif event == _IRQ_GATTC_WRITE_DONE: @@ -86,9 +87,11 @@ def wait_for_event(event, timeout_ms): t0 = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: if event in waiting_events: - return True + result = waiting_events[event] + del waiting_events[event] + return result machine.idle() - return False + raise ValueError("Timeout waiting for {}".format(event)) # Acting in peripheral role. @@ -101,8 +104,6 @@ def instance0(): multitest.next() try: for i in range(7): - waiting_events.clear() - if i == 1: ble.config(mtu=200) elif i == 2: @@ -116,31 +117,26 @@ def instance0(): ble.config(mtu=256) # Wait for central to connect to us. - if not wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS): - return - conn_handle = waiting_events[_IRQ_CENTRAL_CONNECT] + conn_handle = wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) if i >= 4: print("gattc_exchange_mtu") ble.gattc_exchange_mtu(conn_handle) - if not wait_for_event(_IRQ_MTU_EXCHANGED, TIMEOUT_MS): - return - mtu = waiting_events[_IRQ_MTU_EXCHANGED] + mtu = wait_for_event(_IRQ_MTU_EXCHANGED, TIMEOUT_MS) print("gatts_notify") ble.gatts_notify(conn_handle, char_handle, str(i) * 64) - if not wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS): - return + # Extra timeout while client does service discovery. + wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS * 2) print("gatts_read") data = ble.gatts_read(char_handle) print("characteristic len:", len(data), chr(data[0])) # Wait for the central to disconnect. - if not wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS): - return + wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS) print("gap_advertise") ble.gap_advertise(20_000, b"\x02\x01\x06\x04\xffMPY") @@ -154,8 +150,6 @@ def instance1(): multitest.next() try: for i in range(7): - waiting_events.clear() - if i < 4: ble.config(mtu=300) elif i == 5: @@ -166,39 +160,33 @@ def instance1(): ble.config(mtu=256) # Connect to peripheral and then disconnect. + # Extra scan timeout allows for the peripheral to receive the previous disconnect + # event and start advertising again. print("gap_connect") - ble.gap_connect(*BDADDR) - if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): - return - conn_handle = waiting_events[_IRQ_PERIPHERAL_CONNECT] + ble.gap_connect(BDADDR[0], BDADDR[1], 5000) + conn_handle = wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) if i < 4: print("gattc_exchange_mtu") ble.gattc_exchange_mtu(conn_handle) - if not wait_for_event(_IRQ_MTU_EXCHANGED, TIMEOUT_MS): - return - mtu = waiting_events[_IRQ_MTU_EXCHANGED] + mtu = wait_for_event(_IRQ_MTU_EXCHANGED, TIMEOUT_MS) - if not wait_for_event(_IRQ_GATTC_NOTIFY, TIMEOUT_MS): - return + wait_for_event(_IRQ_GATTC_NOTIFY, TIMEOUT_MS) print("gattc_discover_characteristics") ble.gattc_discover_characteristics(conn_handle, 1, 65535) - if not wait_for_event(_IRQ_GATTC_CHARACTERISTIC_DONE, TIMEOUT_MS): - return - value_handle = waiting_events[_IRQ_GATTC_CHARACTERISTIC_RESULT] + value_handle = wait_for_event(_IRQ_GATTC_CHARACTERISTIC_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_CHARACTERISTIC_DONE, TIMEOUT_MS) # Write 20 more than the MTU to test truncation. print("gattc_write") ble.gattc_write(conn_handle, value_handle, chr(ord("a") + i) * (mtu + 20), 1) - if not wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS): - return + wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS) # Disconnect from peripheral. print("gap_disconnect:", ble.gap_disconnect(conn_handle)) - if not wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS): - return + wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS) finally: ble.active(0) From c75ce379100993f41a7502986146e735367cf8aa Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 3 Nov 2020 17:41:58 +1100 Subject: [PATCH 070/179] tests/run-multitests.py: Add a -p flag to run permutations of instances. Signed-off-by: Jim Mussared --- tests/run-multitests.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/run-multitests.py b/tests/run-multitests.py index 5b8e70e921a7a..cdb2730edabbd 100755 --- a/tests/run-multitests.py +++ b/tests/run-multitests.py @@ -6,6 +6,7 @@ import sys, os, time, re, select import argparse +import itertools import subprocess import tempfile @@ -418,6 +419,13 @@ def main(): cmd_parser.add_argument( "-i", "--instance", action="append", default=[], help="instance(s) to run the tests on" ) + cmd_parser.add_argument( + "-p", + "--permutations", + type=int, + default=1, + help="repeat the test with this many permutations of the instance order", + ) cmd_parser.add_argument("files", nargs="+", help="input test files") cmd_args = cmd_parser.parse_args() @@ -450,8 +458,14 @@ def main(): for _ in range(max_instances - len(instances_test)): instances_test.append(PyInstanceSubProcess([MICROPYTHON])) + all_pass = True try: - all_pass = run_tests(test_files, instances_truth, instances_test) + for i, instances_test_permutation in enumerate(itertools.permutations(instances_test)): + if i >= cmd_args.permutations: + break + + all_pass &= run_tests(test_files, instances_truth, instances_test_permutation) + finally: for i in instances_truth: i.close() From 3d890e7ab4b3d85a6fa9e57a9a596a23eeaa6aa7 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Wed, 28 Oct 2020 13:17:23 +1100 Subject: [PATCH 071/179] extmod/modbluetooth: Make UUID type accessible outside modbluetooth.c. Signed-off-by: Jim Mussared --- extmod/btstack/modbluetooth_btstack.c | 1 + extmod/modbluetooth.c | 33 ++++++++++++++------------- extmod/modbluetooth.h | 4 +++- extmod/nimble/modbluetooth_nimble.c | 1 + 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index ae96035868877..fd5d343637b5a 100644 --- a/extmod/btstack/modbluetooth_btstack.c +++ b/extmod/btstack/modbluetooth_btstack.c @@ -75,6 +75,7 @@ STATIC int btstack_error_to_errno(int err) { #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE STATIC mp_obj_bluetooth_uuid_t create_mp_uuid(uint16_t uuid16, const uint8_t *uuid128) { mp_obj_bluetooth_uuid_t result; + result.base.type = &mp_type_bluetooth_uuid; if (uuid16 != 0) { result.data[0] = uuid16 & 0xff; result.data[1] = (uuid16 >> 8) & 0xff; diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 57f69433a1a97..90ed495462b28 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -51,9 +51,8 @@ // while still leaving room for a couple of normal (small, fixed size) events. #define MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN(ringbuf_size) (MAX((int)((ringbuf_size) / 2), (int)(ringbuf_size) - 64)) -STATIC const mp_obj_type_t bluetooth_ble_type; -STATIC const mp_obj_type_t bluetooth_uuid_type; - +// bluetooth.BLE type. This is currently a singleton, however in the future +// this could allow having multiple BLE interfaces on different UARTs. typedef struct { mp_obj_base_t base; mp_obj_t irq_handler; @@ -70,6 +69,8 @@ typedef struct { #endif } mp_obj_bluetooth_ble_t; +STATIC const mp_obj_type_t mp_type_bluetooth_ble; + // TODO: this seems like it could be generic? STATIC mp_obj_t bluetooth_handle_errno(int err) { if (err != 0) { @@ -88,7 +89,7 @@ STATIC mp_obj_t bluetooth_uuid_make_new(const mp_obj_type_t *type, size_t n_args mp_arg_check_num(n_args, n_kw, 1, 1, false); mp_obj_bluetooth_uuid_t *self = m_new_obj(mp_obj_bluetooth_uuid_t); - self->base.type = &bluetooth_uuid_type; + self->base.type = &mp_type_bluetooth_uuid; if (mp_obj_is_int(all_args[0])) { self->type = MP_BLUETOOTH_UUID_TYPE_16; @@ -152,7 +153,7 @@ STATIC mp_obj_t bluetooth_uuid_unary_op(mp_unary_op_t op, mp_obj_t self_in) { } STATIC mp_obj_t bluetooth_uuid_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) { - if (!mp_obj_is_type(rhs_in, &bluetooth_uuid_type)) { + if (!mp_obj_is_type(rhs_in, &mp_type_bluetooth_uuid)) { return MP_OBJ_NULL; } @@ -225,7 +226,7 @@ STATIC void ringbuf_get_uuid(ringbuf_t *ringbuf, mp_obj_bluetooth_uuid_t *uuid) } #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE -STATIC const mp_obj_type_t bluetooth_uuid_type = { +const mp_obj_type_t mp_type_bluetooth_uuid = { { &mp_type_type }, .name = MP_QSTR_UUID, .make_new = bluetooth_uuid_make_new, @@ -247,7 +248,7 @@ STATIC mp_obj_t bluetooth_ble_make_new(const mp_obj_type_t *type, size_t n_args, (void)all_args; if (MP_STATE_VM(bluetooth) == MP_OBJ_NULL) { mp_obj_bluetooth_ble_t *o = m_new0(mp_obj_bluetooth_ble_t, 1); - o->base.type = &bluetooth_ble_type; + o->base.type = &mp_type_bluetooth_ble; o->irq_handler = mp_const_none; @@ -263,7 +264,7 @@ STATIC mp_obj_t bluetooth_ble_make_new(const mp_obj_type_t *type, size_t n_args, mp_obj_memoryview_init(&o->irq_data_addr, 'B', 0, 0, o->irq_data_addr_bytes); o->irq_data_data_alloc = MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN(MICROPY_PY_BLUETOOTH_RINGBUF_SIZE); mp_obj_memoryview_init(&o->irq_data_data, 'B', 0, 0, m_new(uint8_t, o->irq_data_data_alloc)); - o->irq_data_uuid.base.type = &bluetooth_uuid_type; + o->irq_data_uuid.base.type = &mp_type_bluetooth_uuid; // Allocate the default ringbuf. ringbuf_alloc(&o->ringbuf, MICROPY_PY_BLUETOOTH_RINGBUF_SIZE); @@ -441,7 +442,7 @@ STATIC mp_obj_t bluetooth_ble_gap_advertise(size_t n_args, const mp_obj_t *pos_a STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_ble_gap_advertise_obj, 1, bluetooth_ble_gap_advertise); STATIC int bluetooth_gatts_register_service(mp_obj_t uuid_in, mp_obj_t characteristics_in, uint16_t **handles, size_t *num_handles) { - if (!mp_obj_is_type(uuid_in, &bluetooth_uuid_type)) { + if (!mp_obj_is_type(uuid_in, &mp_type_bluetooth_uuid)) { mp_raise_ValueError(MP_ERROR_TEXT("invalid service UUID")); } mp_obj_bluetooth_uuid_t *service_uuid = MP_OBJ_TO_PTR(uuid_in); @@ -482,7 +483,7 @@ STATIC int bluetooth_gatts_register_service(mp_obj_t uuid_in, mp_obj_t character mp_raise_ValueError(MP_ERROR_TEXT("invalid characteristic tuple")); } mp_obj_t uuid_obj = characteristic_items[0]; - if (!mp_obj_is_type(uuid_obj, &bluetooth_uuid_type)) { + if (!mp_obj_is_type(uuid_obj, &mp_type_bluetooth_uuid)) { mp_raise_ValueError(MP_ERROR_TEXT("invalid characteristic UUID")); } @@ -514,7 +515,7 @@ STATIC int bluetooth_gatts_register_service(mp_obj_t uuid_in, mp_obj_t character mp_obj_t *descriptor_items; mp_obj_get_array_fixed_n(descriptor_obj, 2, &descriptor_items); mp_obj_t desc_uuid_obj = descriptor_items[0]; - if (!mp_obj_is_type(desc_uuid_obj, &bluetooth_uuid_type)) { + if (!mp_obj_is_type(desc_uuid_obj, &mp_type_bluetooth_uuid)) { mp_raise_ValueError(MP_ERROR_TEXT("invalid descriptor UUID")); } @@ -717,7 +718,7 @@ STATIC mp_obj_t bluetooth_ble_gattc_discover_services(size_t n_args, const mp_ob mp_int_t conn_handle = mp_obj_get_int(args[1]); mp_obj_bluetooth_uuid_t *uuid = NULL; if (n_args == 3 && args[2] != mp_const_none) { - if (!mp_obj_is_type(args[2], &bluetooth_uuid_type)) { + if (!mp_obj_is_type(args[2], &mp_type_bluetooth_uuid)) { mp_raise_TypeError(MP_ERROR_TEXT("UUID")); } uuid = MP_OBJ_TO_PTR(args[2]); @@ -732,7 +733,7 @@ STATIC mp_obj_t bluetooth_ble_gattc_discover_characteristics(size_t n_args, cons mp_int_t end_handle = mp_obj_get_int(args[3]); mp_obj_bluetooth_uuid_t *uuid = NULL; if (n_args == 5 && args[4] != mp_const_none) { - if (!mp_obj_is_type(args[4], &bluetooth_uuid_type)) { + if (!mp_obj_is_type(args[4], &mp_type_bluetooth_uuid)) { mp_raise_TypeError(MP_ERROR_TEXT("UUID")); } uuid = MP_OBJ_TO_PTR(args[4]); @@ -817,7 +818,7 @@ STATIC const mp_rom_map_elem_t bluetooth_ble_locals_dict_table[] = { }; STATIC MP_DEFINE_CONST_DICT(bluetooth_ble_locals_dict, bluetooth_ble_locals_dict_table); -STATIC const mp_obj_type_t bluetooth_ble_type = { +STATIC const mp_obj_type_t mp_type_bluetooth_ble = { { &mp_type_type }, .name = MP_QSTR_BLE, .make_new = bluetooth_ble_make_new, @@ -826,8 +827,8 @@ STATIC const mp_obj_type_t bluetooth_ble_type = { STATIC const mp_rom_map_elem_t mp_module_bluetooth_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ubluetooth) }, - { MP_ROM_QSTR(MP_QSTR_BLE), MP_ROM_PTR(&bluetooth_ble_type) }, - { MP_ROM_QSTR(MP_QSTR_UUID), MP_ROM_PTR(&bluetooth_uuid_type) }, + { MP_ROM_QSTR(MP_QSTR_BLE), MP_ROM_PTR(&mp_type_bluetooth_ble) }, + { MP_ROM_QSTR(MP_QSTR_UUID), MP_ROM_PTR(&mp_type_bluetooth_uuid) }, { MP_ROM_QSTR(MP_QSTR_FLAG_READ), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ) }, { MP_ROM_QSTR(MP_QSTR_FLAG_WRITE), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE) }, { MP_ROM_QSTR(MP_QSTR_FLAG_NOTIFY), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY) }, diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 2df4c3c25f17b..618939ab18f0e 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -135,7 +135,7 @@ _IRQ_GATTS_INDICATE_DONE = const(20) _IRQ_MTU_EXCHANGED = const(21) */ -// Common UUID type. +// bluetooth.UUID type. // Ports are expected to map this to their own internal UUID types. // Internally the UUID data is little-endian, but the user should only // ever see this if they use the buffer protocol, e.g. in order to @@ -147,6 +147,8 @@ typedef struct { uint8_t data[16]; } mp_obj_bluetooth_uuid_t; +extern const mp_obj_type_t mp_type_bluetooth_uuid; + ////////////////////////////////////////////////////////////// // API implemented by ports (i.e. called from modbluetooth.c): diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index c0d6d64cfc1f0..acb4c03dc32f5 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -114,6 +114,7 @@ STATIC void reverse_addr_byte_order(uint8_t *addr_out, const uint8_t *addr_in) { STATIC mp_obj_bluetooth_uuid_t create_mp_uuid(const ble_uuid_any_t *uuid) { mp_obj_bluetooth_uuid_t result; + result.base.type = &mp_type_bluetooth_uuid; switch (uuid->u.type) { case BLE_UUID_TYPE_16: result.type = MP_BLUETOOTH_UUID_TYPE_16; From de60aa7d6bef3dc25559ae88e36bd05283e927e5 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Fri, 16 Oct 2020 10:10:26 +1100 Subject: [PATCH 072/179] unix: Handle pending events/scheduler in MICROPY_EVENT_POLL_HOOK. Signed-off-by: Jim Mussared --- ports/unix/mpconfigport.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index 17f4895573ed5..d838f42b3f3dc 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -363,7 +363,12 @@ struct _mp_bluetooth_nimble_malloc_t; #define MICROPY_END_ATOMIC_SECTION(x) (void)x; mp_thread_unix_end_atomic_section() #endif -#define MICROPY_EVENT_POLL_HOOK mp_hal_delay_us(500); +#define MICROPY_EVENT_POLL_HOOK \ + do { \ + extern void mp_handle_pending(bool); \ + mp_handle_pending(true); \ + mp_hal_delay_us(500); \ + } while (0); #include #define MICROPY_UNIX_MACHINE_IDLE sched_yield(); From 4559bcb4679e04e0a5e24030675676ff6a9803f2 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 3 Nov 2020 23:21:18 +1100 Subject: [PATCH 073/179] unix: Make mp_hal_delay_ms run MICROPY_EVENT_POLL_HOOK. Signed-off-by: Jim Mussared --- ports/unix/mphalport.h | 5 ----- ports/unix/unix_mphal.c | 14 ++++++++++++++ ports/windows/windows_mphal.c | 6 ++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/ports/unix/mphalport.h b/ports/unix/mphalport.h index 95ad63221ef95..89d23ca5d22e9 100644 --- a/ports/unix/mphalport.h +++ b/ports/unix/mphalport.h @@ -64,11 +64,6 @@ static inline int mp_hal_readline(vstr_t *vstr, const char *p) { #endif -// TODO: POSIX et al. define usleep() as guaranteedly capable only of 1s sleep: -// "The useconds argument shall be less than one million." -static inline void mp_hal_delay_ms(mp_uint_t ms) { - usleep((ms) * 1000); -} static inline void mp_hal_delay_us(mp_uint_t us) { usleep(us); } diff --git a/ports/unix/unix_mphal.c b/ports/unix/unix_mphal.c index 3fe2fa9fec21d..28a4ca2c4a8e9 100644 --- a/ports/unix/unix_mphal.c +++ b/ports/unix/unix_mphal.c @@ -220,3 +220,17 @@ uint64_t mp_hal_time_ns(void) { gettimeofday(&tv, NULL); return (uint64_t)tv.tv_sec * 1000000000ULL + (uint64_t)tv.tv_usec * 1000ULL; } + +void mp_hal_delay_ms(mp_uint_t ms) { + #ifdef MICROPY_EVENT_POLL_HOOK + mp_uint_t start = mp_hal_ticks_ms(); + while (mp_hal_ticks_ms() - start < ms) { + // MICROPY_EVENT_POLL_HOOK does mp_hal_delay_us(500) (i.e. usleep(500)). + MICROPY_EVENT_POLL_HOOK + } + #else + // TODO: POSIX et al. define usleep() as guaranteedly capable only of 1s sleep: + // "The useconds argument shall be less than one million." + usleep(ms * 1000); + #endif +} diff --git a/ports/windows/windows_mphal.c b/ports/windows/windows_mphal.c index b442b59147e4e..49daa0542868f 100644 --- a/ports/windows/windows_mphal.c +++ b/ports/windows/windows_mphal.c @@ -261,3 +261,9 @@ uint64_t mp_hal_time_ns(void) { gettimeofday(&tv, NULL); return (uint64_t)tv.tv_sec * 1000000000ULL + (uint64_t)tv.tv_usec * 1000ULL; } + +// TODO: POSIX et al. define usleep() as guaranteedly capable only of 1s sleep: +// "The useconds argument shall be less than one million." +void mp_hal_delay_ms(mp_uint_t ms) { + usleep((ms) * 1000); +} From c398e46b29f5c780b8016f2e88afe4c6984c54d8 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 3 Nov 2020 17:46:11 +1100 Subject: [PATCH 074/179] extmod/modbluetooth: Combine gattc-data-available callbacks into one. Instead of having the stack indicate a "start", "data"..., "end", pass through the data in one callback as an array of chunks of data. This is because the upcoming non-ringbuffer modbluetooth implementation cannot buffer the data in the ringbuffer and requires instead a single callback with all the data, to pass to the Python callback. Signed-off-by: Jim Mussared --- extmod/btstack/modbluetooth_btstack.c | 15 ++-------- extmod/modbluetooth.c | 41 ++++++++++++++------------- extmod/modbluetooth.h | 6 +--- extmod/nimble/modbluetooth_nimble.c | 35 +++++++++++++++++------ 4 files changed, 53 insertions(+), 44 deletions(-) diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index fd5d343637b5a..a4cc601746b1e 100644 --- a/extmod/btstack/modbluetooth_btstack.c +++ b/extmod/btstack/modbluetooth_btstack.c @@ -414,30 +414,21 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t uint16_t value_handle = gatt_event_characteristic_value_query_result_get_value_handle(packet); uint16_t len = gatt_event_characteristic_value_query_result_get_value_length(packet); const uint8_t *data = gatt_event_characteristic_value_query_result_get_value(packet); - mp_uint_t atomic_state; - len = mp_bluetooth_gattc_on_data_available_start(MP_BLUETOOTH_IRQ_GATTC_READ_RESULT, conn_handle, value_handle, len, &atomic_state); - mp_bluetooth_gattc_on_data_available_chunk(data, len); - mp_bluetooth_gattc_on_data_available_end(atomic_state); + mp_bluetooth_gattc_on_data_available(MP_BLUETOOTH_IRQ_GATTC_READ_RESULT, conn_handle, value_handle, &data, &len, 1); } else if (event_type == GATT_EVENT_NOTIFICATION) { DEBUG_printf(" --> gatt notification\n"); uint16_t conn_handle = gatt_event_notification_get_handle(packet); uint16_t value_handle = gatt_event_notification_get_value_handle(packet); uint16_t len = gatt_event_notification_get_value_length(packet); const uint8_t *data = gatt_event_notification_get_value(packet); - mp_uint_t atomic_state; - len = mp_bluetooth_gattc_on_data_available_start(MP_BLUETOOTH_IRQ_GATTC_NOTIFY, conn_handle, value_handle, len, &atomic_state); - mp_bluetooth_gattc_on_data_available_chunk(data, len); - mp_bluetooth_gattc_on_data_available_end(atomic_state); + mp_bluetooth_gattc_on_data_available(MP_BLUETOOTH_IRQ_GATTC_NOTIFY, conn_handle, value_handle, &data, &len, 1); } else if (event_type == GATT_EVENT_INDICATION) { DEBUG_printf(" --> gatt indication\n"); uint16_t conn_handle = gatt_event_indication_get_handle(packet); uint16_t value_handle = gatt_event_indication_get_value_handle(packet); uint16_t len = gatt_event_indication_get_value_length(packet); const uint8_t *data = gatt_event_indication_get_value(packet); - mp_uint_t atomic_state; - len = mp_bluetooth_gattc_on_data_available_start(MP_BLUETOOTH_IRQ_GATTC_INDICATE, conn_handle, value_handle, len, &atomic_state); - mp_bluetooth_gattc_on_data_available_chunk(data, len); - mp_bluetooth_gattc_on_data_available_end(atomic_state); + mp_bluetooth_gattc_on_data_available(MP_BLUETOOTH_IRQ_GATTC_INDICATE, conn_handle, value_handle, &data, &len, 1); } else if (event_type == GATT_EVENT_CAN_WRITE_WITHOUT_RESPONSE) { uint16_t conn_handle = gatt_event_can_write_without_response_get_handle(packet); DEBUG_printf(" --> gatt can write without response %d\n", conn_handle); diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 90ed495462b28..6bad134959271 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -1125,31 +1125,34 @@ void mp_bluetooth_gattc_on_discover_complete(uint8_t event, uint16_t conn_handle schedule_ringbuf(atomic_state); } -size_t mp_bluetooth_gattc_on_data_available_start(uint8_t event, uint16_t conn_handle, uint16_t value_handle, size_t data_len, mp_uint_t *atomic_state_out) { +void mp_bluetooth_gattc_on_data_available(uint8_t event, uint16_t conn_handle, uint16_t value_handle, const uint8_t **data, uint16_t *data_len, size_t num) { MICROPY_PY_BLUETOOTH_ENTER - *atomic_state_out = atomic_state; mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - data_len = MIN(o->irq_data_data_alloc, data_len); - if (enqueue_irq(o, 2 + 2 + 2 + data_len, event)) { + + // Get the total length of the fragmented buffers. + uint16_t total_len = 0; + for (size_t i = 0; i < num; ++i) { + total_len += data_len[i]; + } + + // Truncate the data at what we'll be able to pass to Python. + total_len = MIN(o->irq_data_data_alloc, total_len); + + if (enqueue_irq(o, 2 + 2 + 2 + total_len, event)) { ringbuf_put16(&o->ringbuf, conn_handle); ringbuf_put16(&o->ringbuf, value_handle); - // Length field is 16-bit. - data_len = MIN(UINT16_MAX, data_len); - ringbuf_put16(&o->ringbuf, data_len); - return data_len; - } else { - return 0; - } -} -void mp_bluetooth_gattc_on_data_available_chunk(const uint8_t *data, size_t data_len) { - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - for (size_t i = 0; i < data_len; ++i) { - ringbuf_put(&o->ringbuf, data[i]); - } -} + ringbuf_put16(&o->ringbuf, total_len); -void mp_bluetooth_gattc_on_data_available_end(mp_uint_t atomic_state) { + // Copy total_len from the fragments to the ringbuffer. + uint16_t copied_bytes = 0; + for (size_t i = 0; i < num; ++i) { + for (size_t j = 0; i < data_len[i] && copied_bytes < total_len; ++j) { + ringbuf_put(&o->ringbuf, data[i][j]); + ++copied_bytes; + } + } + } schedule_ringbuf(atomic_state); } diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 618939ab18f0e..dee7186a2db3f 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -289,11 +289,7 @@ void mp_bluetooth_gattc_on_descriptor_result(uint16_t conn_handle, uint16_t hand void mp_bluetooth_gattc_on_discover_complete(uint8_t event, uint16_t conn_handle, uint16_t status); // Notify modbluetooth that a read has completed with data (or notify/indicate data available, use `event` to disambiguate). -// Note: these functions are to be called in a group protected by MICROPY_PY_BLUETOOTH_ENTER/EXIT. -// _start returns the number of bytes to submit to the calls to _chunk, followed by a call to _end. -size_t mp_bluetooth_gattc_on_data_available_start(uint8_t event, uint16_t conn_handle, uint16_t value_handle, size_t data_len, mp_uint_t *atomic_state_out); -void mp_bluetooth_gattc_on_data_available_chunk(const uint8_t *data, size_t data_len); -void mp_bluetooth_gattc_on_data_available_end(mp_uint_t atomic_state); +void mp_bluetooth_gattc_on_data_available(uint8_t event, uint16_t conn_handle, uint16_t value_handle, const uint8_t **data, uint16_t *data_len, size_t num); // Notify modbluetooth that a read or write operation has completed. void mp_bluetooth_gattc_on_read_write_status(uint8_t event, uint16_t conn_handle, uint16_t value_handle, uint16_t status); diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index acb4c03dc32f5..621278e0f15c8 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -798,16 +798,35 @@ int mp_bluetooth_set_preferred_mtu(uint16_t mtu) { #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE STATIC void gattc_on_data_available(uint8_t event, uint16_t conn_handle, uint16_t value_handle, const struct os_mbuf *om) { - size_t len = OS_MBUF_PKTLEN(om); - mp_uint_t atomic_state; - len = mp_bluetooth_gattc_on_data_available_start(event, conn_handle, value_handle, len, &atomic_state); - while (len > 0 && om != NULL) { - size_t n = MIN(om->om_len, len); - mp_bluetooth_gattc_on_data_available_chunk(OS_MBUF_DATA(om, const uint8_t *), n); - len -= n; + // When the HCI data for an ATT payload arrives, the L2CAP channel will + // buffer it into its receive buffer. We set BLE_L2CAP_JOIN_RX_FRAGS=1 in + // syscfg.h so it should be rare that the mbuf is fragmented, but we do need + // to be able to handle it. We pass all the fragments up to modbluetooth.c + // which will create a temporary buffer on the MicroPython heap if necessary + // to re-assemble them. + + // Count how many links are in the mbuf chain. + size_t n = 0; + const struct os_mbuf *elem = om; + while (elem) { + n += 1; + elem = SLIST_NEXT(elem, om_next); + } + + // Grab data pointers and lengths for each of the links. + const uint8_t **data = mp_local_alloc(sizeof(uint8_t *) * n); + uint16_t *data_len = mp_local_alloc(sizeof(uint16_t) * n); + for (size_t i = 0; i < n; ++i) { + data[i] = OS_MBUF_DATA(om, const uint8_t *); + data_len[i] = om->om_len; om = SLIST_NEXT(om, om_next); } - mp_bluetooth_gattc_on_data_available_end(atomic_state); + + // Pass all the fragments together. + mp_bluetooth_gattc_on_data_available(event, conn_handle, value_handle, data, data_len, n); + + mp_local_free(data_len); + mp_local_free(data); } STATIC int gap_scan_cb(struct ble_gap_event *event, void *arg) { From 6d9fdff8d07f3fa2a05eddb05e1a55754ae3542f Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 3 Nov 2020 17:48:55 +1100 Subject: [PATCH 075/179] extmod/nimble: Poll startup directly rather than using NimBLE sem. Using a semaphore (the previous approach) will only run the UART, whereas for startup we need to also run the event queue. This change makes it run the full scheduler hook. Signed-off-by: Jim Mussared --- extmod/nimble/modbluetooth_nimble.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 621278e0f15c8..b387e6ae1f979 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -53,7 +53,6 @@ STATIC uint8_t nimble_address_mode = BLE_OWN_ADDR_RANDOM; #define NIMBLE_STARTUP_TIMEOUT 2000 -STATIC struct ble_npl_sem startup_sem; // Any BLE_HS_xxx code not in this table will default to MP_EIO. STATIC int8_t ble_hs_err_to_errno_table[] = { @@ -210,8 +209,6 @@ STATIC void sync_cb(void) { ble_svc_gap_device_name_set(MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME); mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE; - - ble_npl_sem_release(&startup_sem); } STATIC void gatts_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) { @@ -369,8 +366,6 @@ int mp_bluetooth_init(void) { ble_hs_cfg.gatts_register_cb = gatts_register_cb; ble_hs_cfg.store_status_cb = ble_store_util_status_rr; - ble_npl_sem_init(&startup_sem, 0); - MP_STATE_PORT(bluetooth_nimble_root_pointers) = m_new0(mp_bluetooth_nimble_root_pointers_t, 1); mp_bluetooth_gatts_db_create(&MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db); @@ -383,16 +378,24 @@ int mp_bluetooth_init(void) { // Otherwise default implementation above calls ble_hci_uart_init(). mp_bluetooth_nimble_port_hci_init(); + // Static initialization is complete, can start processing events. + mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC; + // Initialise NimBLE memory and data structures. nimble_port_init(); // Make sure that the HCI UART and event handling task is running. mp_bluetooth_nimble_port_start(); - // Static initialization is complete, can start processing events. - mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC; - - ble_npl_sem_pend(&startup_sem, NIMBLE_STARTUP_TIMEOUT); + // Run the scheduler while we wait for stack startup. + // On non-ringbuffer builds (NimBLE on STM32/Unix) this will also poll the UART and run the event queue. + mp_uint_t timeout_start_ticks_ms = mp_hal_ticks_ms(); + while (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE) { + if (mp_hal_ticks_ms() - timeout_start_ticks_ms > NIMBLE_STARTUP_TIMEOUT) { + break; + } + MICROPY_EVENT_POLL_HOOK + } if (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE) { mp_bluetooth_deinit(); From 81e92d3d6e1a605a6115821ac24dcbc2546ba0f9 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Wed, 28 Oct 2020 13:06:45 +1100 Subject: [PATCH 076/179] extmod/modbluetooth: Re-instate optional no-ringbuf modbluetooth. This requires that the event handlers are called from non-interrupt context (i.e. the MicroPython scheduler). This will allow the BLE stack (e.g. NimBLE) to run from the scheduler rather than an IRQ like PENDSV, and therefore be able to invoke Python callbacks directly/synchronously. This allows writing Python BLE handlers for events that require immediate response such as _IRQ_READ_REQUEST (which was previous a hard IRQ) and future events relating to pairing/bonding. Signed-off-by: Jim Mussared --- extmod/btstack/modbluetooth_btstack.c | 4 +- extmod/modbluetooth.c | 224 +++++++++++++++++++++----- extmod/modbluetooth.h | 8 +- extmod/nimble/modbluetooth_nimble.c | 4 +- ports/stm32/Makefile | 1 - ports/unix/Makefile | 2 - 6 files changed, 189 insertions(+), 54 deletions(-) diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index a4cc601746b1e..97dd2cbb53f5c 100644 --- a/extmod/btstack/modbluetooth_btstack.c +++ b/extmod/btstack/modbluetooth_btstack.c @@ -838,15 +838,15 @@ STATIC uint16_t att_read_callback(hci_con_handle_t connection_handle, uint16_t a return 0; } - #if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK // Allow Python code to override value (by using gatts_write), or deny (by returning false) the read. + // Note this will be a no-op if the ringbuffer implementation is being used, as the Python callback cannot + // be executed synchronously. This is currently always the case for btstack. if ((buffer == NULL) && (buffer_size == 0)) { if (!mp_bluetooth_gatts_on_read_request(connection_handle, att_handle)) { DEBUG_printf("att_read_callback: read request denied\n"); return 0; } } - #endif uint16_t ret = att_read_callback_handle_blob(entry->data, entry->data_len, offset, buffer, buffer_size); return ret; diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 6bad134959271..d8068df594538 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -47,15 +47,18 @@ #define MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN 5 +#if !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS // This formula is intended to allow queuing the data of a large characteristic // while still leaving room for a couple of normal (small, fixed size) events. #define MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN(ringbuf_size) (MAX((int)((ringbuf_size) / 2), (int)(ringbuf_size) - 64)) +#endif // bluetooth.BLE type. This is currently a singleton, however in the future // this could allow having multiple BLE interfaces on different UARTs. typedef struct { mp_obj_base_t base; mp_obj_t irq_handler; + #if !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS bool irq_scheduled; mp_obj_t irq_data_tuple; uint8_t irq_data_addr_bytes[6]; @@ -64,8 +67,6 @@ typedef struct { mp_obj_array_t irq_data_data; mp_obj_bluetooth_uuid_t irq_data_uuid; ringbuf_t ringbuf; - #if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK - mp_obj_t irq_read_request_data_tuple; #endif } mp_obj_bluetooth_ble_t; @@ -206,8 +207,7 @@ STATIC mp_int_t bluetooth_uuid_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bu return 0; } -#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE - +#if !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS && MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE STATIC void ringbuf_put_uuid(ringbuf_t *ringbuf, mp_obj_bluetooth_uuid_t *uuid) { assert(ringbuf_free(ringbuf) >= (size_t)uuid->type + 1); ringbuf_put(ringbuf, uuid->type); @@ -252,14 +252,10 @@ STATIC mp_obj_t bluetooth_ble_make_new(const mp_obj_type_t *type, size_t n_args, o->irq_handler = mp_const_none; + #if !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS // Pre-allocate the event data tuple to prevent needing to allocate in the IRQ handler. o->irq_data_tuple = mp_obj_new_tuple(MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN, NULL); - #if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK - // Pre-allocate a separate data tuple for the read request "hard" irq. - o->irq_read_request_data_tuple = mp_obj_new_tuple(2, NULL); - #endif - // Pre-allocated buffers for address, payload and uuid. mp_obj_memoryview_init(&o->irq_data_addr, 'B', 0, 0, o->irq_data_addr_bytes); o->irq_data_data_alloc = MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN(MICROPY_PY_BLUETOOTH_RINGBUF_SIZE); @@ -268,6 +264,7 @@ STATIC mp_obj_t bluetooth_ble_make_new(const mp_obj_type_t *type, size_t n_args, // Allocate the default ringbuf. ringbuf_alloc(&o->ringbuf, MICROPY_PY_BLUETOOTH_RINGBUF_SIZE); + #endif MP_STATE_VM(bluetooth) = MP_OBJ_FROM_PTR(o); } @@ -290,8 +287,6 @@ STATIC mp_obj_t bluetooth_ble_active(size_t n_args, const mp_obj_t *args) { STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_active_obj, 1, 2, bluetooth_ble_active); STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { - mp_obj_bluetooth_ble_t *self = MP_OBJ_TO_PTR(args[0]); - if (kwargs->used == 0) { // Get config value if (n_args != 2) { @@ -311,8 +306,12 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map mp_obj_t items[] = { MP_OBJ_NEW_SMALL_INT(addr_type), mp_obj_new_bytes(addr, MP_ARRAY_SIZE(addr)) }; return mp_obj_new_tuple(2, items); } - case MP_QSTR_rxbuf: + #if !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS + case MP_QSTR_rxbuf: { + mp_obj_bluetooth_ble_t *self = MP_OBJ_TO_PTR(args[0]); return mp_obj_new_int(self->ringbuf.size); + } + #endif case MP_QSTR_mtu: return mp_obj_new_int(mp_bluetooth_get_preferred_mtu()); default: @@ -334,6 +333,7 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map bluetooth_handle_errno(mp_bluetooth_gap_set_device_name(bufinfo.buf, bufinfo.len)); break; } + #if !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS case MP_QSTR_rxbuf: { // Determine new buffer sizes mp_int_t ringbuf_alloc = mp_obj_get_int(e->value); @@ -347,6 +347,7 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map uint8_t *irq_data = m_new(uint8_t, irq_data_alloc); // Get old buffer sizes and pointers + mp_obj_bluetooth_ble_t *self = MP_OBJ_TO_PTR(args[0]); uint8_t *old_ringbuf_buf = self->ringbuf.buf; size_t old_ringbuf_alloc = self->ringbuf.size; uint8_t *old_irq_data_buf = (uint8_t *)self->irq_data_data.items; @@ -367,6 +368,7 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map m_del(uint8_t, old_irq_data_buf, old_irq_data_alloc); break; } + #endif case MP_QSTR_mtu: { mp_int_t mtu = mp_obj_get_int(e->value); bluetooth_handle_errno(mp_bluetooth_set_preferred_mtu(mtu)); @@ -845,8 +847,7 @@ const mp_obj_module_t mp_module_ubluetooth = { // Helpers -#include - +#if !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS STATIC void ringbuf_extract(ringbuf_t *ringbuf, mp_obj_tuple_t *data_tuple, size_t n_u16, size_t n_u8, mp_obj_array_t *bytes_addr, size_t n_i8, mp_obj_bluetooth_uuid_t *uuid, mp_obj_array_t *bytes_data) { assert(ringbuf_avail(ringbuf) >= n_u16 * 2 + n_u8 + (bytes_addr ? 6 : 0) + n_i8 + (uuid ? 1 : 0) + (bytes_data ? 1 : 0)); size_t j = 0; @@ -854,7 +855,7 @@ STATIC void ringbuf_extract(ringbuf_t *ringbuf, mp_obj_tuple_t *data_tuple, size for (size_t i = 0; i < n_u16; ++i) { data_tuple->items[j++] = MP_OBJ_NEW_SMALL_INT(ringbuf_get16(ringbuf)); } - if (n_u8) { + for (size_t i = 0; i < n_u8; ++i) { data_tuple->items[j++] = MP_OBJ_NEW_SMALL_INT(ringbuf_get(ringbuf)); } if (bytes_addr) { @@ -960,11 +961,166 @@ STATIC mp_obj_t bluetooth_ble_invoke_irq(mp_obj_t none_in) { return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(bluetooth_ble_invoke_irq_obj, bluetooth_ble_invoke_irq); +#endif // !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS // ---------------------------------------------------------------------------- // Port API // ---------------------------------------------------------------------------- +#if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS + +STATIC mp_obj_t invoke_irq_handler(uint16_t event, + const uint16_t *u16, size_t n_u16, + const uint8_t *u8, size_t n_u8, + const uint8_t *addr, + const int8_t *i8, size_t n_i8, + const mp_obj_bluetooth_uuid_t *uuid, + const uint8_t *data, size_t data_len) { + mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); + if (o->irq_handler == mp_const_none) { + return mp_const_none; + } + + mp_obj_array_t mv_addr; + mp_obj_array_t mv_data; + + mp_obj_tuple_t *data_tuple = mp_local_alloc(sizeof(mp_obj_tuple_t) + sizeof(mp_obj_t) * MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN); + data_tuple->base.type = &mp_type_tuple; + data_tuple->len = 0; + + for (size_t i = 0; i < n_u16; ++i) { + data_tuple->items[data_tuple->len++] = MP_OBJ_NEW_SMALL_INT(u16[i]); + } + for (size_t i = 0; i < n_u8; ++i) { + data_tuple->items[data_tuple->len++] = MP_OBJ_NEW_SMALL_INT(u8[i]); + } + if (addr) { + mp_obj_memoryview_init(&mv_addr, 'B', 0, 6, (void *)addr); + data_tuple->items[data_tuple->len++] = MP_OBJ_FROM_PTR(&mv_addr); + } + for (size_t i = 0; i < n_i8; ++i) { + data_tuple->items[data_tuple->len++] = MP_OBJ_NEW_SMALL_INT(i8[i]); + } + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + if (uuid) { + data_tuple->items[data_tuple->len++] = MP_OBJ_FROM_PTR(uuid); + } + #endif + if (data) { + mp_obj_memoryview_init(&mv_data, 'B', 0, data_len, (void *)data); + data_tuple->items[data_tuple->len++] = MP_OBJ_FROM_PTR(&mv_data); + } + assert(data_tuple->len <= MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN); + + mp_obj_t result = mp_call_function_2(o->irq_handler, MP_OBJ_NEW_SMALL_INT(event), MP_OBJ_FROM_PTR(data_tuple)); + + mp_local_free(data_tuple); + + return result; +} + +#define NULL_U16 NULL +#define NULL_U8 NULL +#define NULL_ADDR NULL +#define NULL_I8 NULL +#define NULL_UUID NULL +#define NULL_DATA NULL + +void mp_bluetooth_gap_on_connected_disconnected(uint8_t event, uint16_t conn_handle, uint8_t addr_type, const uint8_t *addr) { + invoke_irq_handler(event, &conn_handle, 1, &addr_type, 1, addr, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); +} + +void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle) { + uint16_t args[] = {conn_handle, value_handle}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_WRITE, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); +} + +void mp_bluetooth_gatts_on_indicate_complete(uint16_t conn_handle, uint16_t value_handle, uint8_t status) { + uint16_t args[] = {conn_handle, value_handle}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_INDICATE_DONE, args, 2, &status, 1, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); +} + +bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle) { + uint16_t args[] = {conn_handle, value_handle}; + mp_obj_t result = invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST, args, 2, NULL, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); + return result == mp_const_none || mp_obj_is_true(result); +} + +void mp_bluetooth_gatts_on_mtu_exchanged(uint16_t conn_handle, uint16_t value) { + uint16_t args[] = {conn_handle, value}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_MTU_EXCHANGED, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); +} + +#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +void mp_bluetooth_gap_on_scan_complete(void) { + invoke_irq_handler(MP_BLUETOOTH_IRQ_SCAN_DONE, NULL_U16, 0, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); +} + +void mp_bluetooth_gap_on_scan_result(uint8_t addr_type, const uint8_t *addr, uint8_t adv_type, const int8_t rssi, const uint8_t *data, size_t data_len) { + int8_t args[] = {adv_type, rssi}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_SCAN_RESULT, NULL_U16, 0, &addr_type, 1, addr, args, 2, NULL_UUID, data, data_len); +} + +void mp_bluetooth_gattc_on_primary_service_result(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle, mp_obj_bluetooth_uuid_t *service_uuid) { + uint16_t args[] = {conn_handle, start_handle, end_handle}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT, args, 3, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, service_uuid, NULL_DATA, 0); +} + +void mp_bluetooth_gattc_on_characteristic_result(uint16_t conn_handle, uint16_t def_handle, uint16_t value_handle, uint8_t properties, mp_obj_bluetooth_uuid_t *characteristic_uuid) { + uint16_t args[] = {conn_handle, def_handle, value_handle}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT, args, 3, &properties, 1, NULL_ADDR, NULL_I8, 0, characteristic_uuid, NULL_DATA, 0); +} + +void mp_bluetooth_gattc_on_descriptor_result(uint16_t conn_handle, uint16_t handle, mp_obj_bluetooth_uuid_t *descriptor_uuid) { + uint16_t args[] = {conn_handle, handle}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, descriptor_uuid, NULL_DATA, 0); +} + +void mp_bluetooth_gattc_on_discover_complete(uint8_t event, uint16_t conn_handle, uint16_t status) { + uint16_t args[] = {conn_handle, status}; + invoke_irq_handler(event, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); +} + +void mp_bluetooth_gattc_on_data_available(uint8_t event, uint16_t conn_handle, uint16_t value_handle, const uint8_t **data, uint16_t *data_len, size_t num) { + const uint8_t *combined_data; + uint16_t total_len; + + if (num > 1) { + // Fragmented buffer, need to combine into a new heap-allocated buffer + // in order to pass to Python. + total_len = 0; + for (size_t i = 0; i < num; ++i) { + total_len += data_len[i]; + } + uint8_t *buf = m_new(uint8_t, total_len); + uint8_t *p = buf; + for (size_t i = 0; i < num; ++i) { + memcpy(p, data[i], data_len[i]); + p += data_len[i]; + } + combined_data = buf; + } else { + // Single buffer, use directly. + combined_data = *data; + total_len = *data_len; + } + + uint16_t args[] = {conn_handle, value_handle}; + invoke_irq_handler(event, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, combined_data, total_len); + + if (num > 1) { + m_del(uint8_t, (uint8_t *)combined_data, total_len); + } +} + +void mp_bluetooth_gattc_on_read_write_status(uint8_t event, uint16_t conn_handle, uint16_t value_handle, uint16_t status) { + uint16_t args[] = {conn_handle, value_handle, status}; + invoke_irq_handler(event, args, 3, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); +} + +#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + +#else // !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS // Callbacks are called in interrupt context (i.e. can't allocate), so we need to push the data // into the ringbuf and schedule the callback via mp_sched_schedule. @@ -1169,36 +1325,12 @@ void mp_bluetooth_gattc_on_read_write_status(uint8_t event, uint16_t conn_handle #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE -#if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK -// This can only be enabled when the thread invoking this is a MicroPython thread. -// On ESP32, for example, this is not the case. bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle) { - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if (o->irq_handler != mp_const_none) { - // When executing code within a handler we must lock the scheduler to - // prevent any scheduled callbacks from running, and lock the GC to - // prevent any memory allocations. - mp_sched_lock(); - gc_lock(); - - // Use pre-allocated tuple distinct to the one used by the "soft" IRQs. - mp_obj_tuple_t *data = MP_OBJ_TO_PTR(o->irq_read_request_data_tuple); - data->items[0] = MP_OBJ_NEW_SMALL_INT(conn_handle); - data->items[1] = MP_OBJ_NEW_SMALL_INT(value_handle); - data->len = 2; - mp_obj_t irq_ret = mp_call_function_2_protected(o->irq_handler, MP_OBJ_NEW_SMALL_INT(MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST), o->irq_read_request_data_tuple); - - gc_unlock(); - mp_sched_unlock(); - - // If the IRQ handler explicitly returned false, then deny the read. Otherwise if it returns None/True, allow it. - return irq_ret != MP_OBJ_NULL && (irq_ret == mp_const_none || mp_obj_is_true(irq_ret)); - } else { - // No IRQ handler, allow the read. - return true; - } + (void)conn_handle; + (void)value_handle; + // This must be handled synchronously and therefore cannot implemented with the ringbuffer. + return true; } -#endif void mp_bluetooth_gatts_on_mtu_exchanged(uint16_t conn_handle, uint16_t value) { MICROPY_PY_BLUETOOTH_ENTER @@ -1210,6 +1342,12 @@ void mp_bluetooth_gatts_on_mtu_exchanged(uint16_t conn_handle, uint16_t value) { schedule_ringbuf(atomic_state); } +#endif // MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS + +// ---------------------------------------------------------------------------- +// GATTS DB +// ---------------------------------------------------------------------------- + void mp_bluetooth_gatts_db_create_entry(mp_gatts_db_t db, uint16_t handle, size_t len) { mp_map_elem_t *elem = mp_map_lookup(db, MP_OBJ_NEW_SMALL_INT(handle), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); mp_bluetooth_gatts_db_entry_t *entry = m_new(mp_bluetooth_gatts_db_entry_t, 1); diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index dee7186a2db3f..6ed086d553f05 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -43,8 +43,10 @@ #define MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE (0) #endif -#ifndef MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK -#define MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK (0) +#ifndef MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS +// This can be enabled if the BLE stack runs entirely in scheduler context +// and therefore is able to call directly into the VM to run Python callbacks. +#define MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS (0) #endif // This is used to protect the ringbuffer. @@ -261,10 +263,8 @@ void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle); // Call this when an acknowledgment is received for an indication. void mp_bluetooth_gatts_on_indicate_complete(uint16_t conn_handle, uint16_t value_handle, uint8_t status); -#if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK // Call this when a characteristic is read from. Return false to deny the read. bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle); -#endif // Call this when an MTU exchange completes. void mp_bluetooth_gatts_on_mtu_exchanged(uint16_t conn_handle, uint16_t value); diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index b387e6ae1f979..7ee6ae8677e8b 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -586,12 +586,12 @@ static int characteristic_access_cb(uint16_t conn_handle, uint16_t value_handle, switch (ctxt->op) { case BLE_GATT_ACCESS_OP_READ_CHR: case BLE_GATT_ACCESS_OP_READ_DSC: - #if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK // Allow Python code to override (by using gatts_write), or deny (by returning false) the read. + // Note this will be a no-op if the ringbuffer implementation is being used (i.e. the stack isn't + // run in the scheduler). The ringbuffer is not used on STM32 and Unix-H4 only. if (!mp_bluetooth_gatts_on_read_request(conn_handle, value_handle)) { return BLE_ATT_ERR_READ_NOT_PERMITTED; } - #endif entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, value_handle); if (!entry) { diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 18f7e8a1a10d8..75090a077e2bf 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -430,7 +430,6 @@ USBDEV_SRC_C += $(addprefix $(USBDEV_DIR)/,\ ifeq ($(MICROPY_PY_BLUETOOTH),1) CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH=1 CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE=1 -CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK=1 endif ifeq ($(MICROPY_PY_NETWORK_CYW43),1) diff --git a/ports/unix/Makefile b/ports/unix/Makefile index 048ef97f77531..f72c05f1add78 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -156,8 +156,6 @@ endif CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH=1 CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE=1 -# Runs in a thread, cannot make calls into the VM. -CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK=0 ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) From 61d1e4b01b1bf77e5ca478e18065f0691ae274a7 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 3 Nov 2020 23:27:47 +1100 Subject: [PATCH 077/179] extmod/nimble: Make stm32 and unix NimBLE ports use synchronous events. This changes stm32 from using PENDSV to run NimBLE to use the MicroPython scheduler instead. This allows Python BLE callbacks to be invoked directly (and therefore synchronously) rather than via the ringbuffer. The NimBLE UART HCI and event processing now happens in a scheduled task every 128ms. When RX IRQ idle events arrive, it will also schedule this task to improve latency. There is a similar change for the unix port where the background thread now queues the scheduled task. Signed-off-by: Jim Mussared --- extmod/modbluetooth.h | 1 + extmod/nimble/hal/hal_uart.c | 23 +++- extmod/nimble/hal/hal_uart.h | 2 +- extmod/nimble/modbluetooth_nimble.c | 5 +- extmod/nimble/nimble.mk | 7 + extmod/nimble/nimble/nimble_npl_os.c | 189 ++++++++++++++------------- extmod/nimble/nimble/nimble_npl_os.h | 4 +- ports/stm32/main.c | 4 +- ports/stm32/mpbthciport.c | 65 ++++++++- ports/stm32/mpconfigport.h | 9 +- ports/stm32/mpnimbleport.c | 30 ++--- ports/stm32/pendsv.h | 2 +- ports/stm32/rfcore.c | 6 +- ports/unix/mpbthciport.c | 54 +++++++- ports/unix/mpnimbleport.c | 34 ++--- 15 files changed, 279 insertions(+), 156 deletions(-) diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 6ed086d553f05..52e3446ff3b94 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -50,6 +50,7 @@ #endif // This is used to protect the ringbuffer. +// A port may no-op this if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS is enabled. #ifndef MICROPY_PY_BLUETOOTH_ENTER #define MICROPY_PY_BLUETOOTH_ENTER mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); #define MICROPY_PY_BLUETOOTH_EXIT MICROPY_END_ATOMIC_SECTION(atomic_state); diff --git a/extmod/nimble/hal/hal_uart.c b/extmod/nimble/hal/hal_uart.c index c6d0850fea993..230970b089c55 100644 --- a/extmod/nimble/hal/hal_uart.c +++ b/extmod/nimble/hal/hal_uart.c @@ -28,10 +28,13 @@ #include "py/mphal.h" #include "nimble/ble.h" #include "extmod/nimble/hal/hal_uart.h" +#include "extmod/nimble/nimble/nimble_npl_os.h" #include "extmod/mpbthci.h" #if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE +#define HCI_TRACE (0) + static hal_uart_tx_cb_t hal_uart_tx_cb; static void *hal_uart_tx_arg; static hal_uart_rx_cb_t hal_uart_rx_cb; @@ -62,10 +65,10 @@ void hal_uart_start_tx(uint32_t port) { mp_bluetooth_hci_cmd_buf[len++] = data; } - #if 0 - printf("[% 8d] BTUTX: %02x", mp_hal_ticks_ms(), hci_cmd_buf[0]); - for (int i = 1; i < len; ++i) { - printf(":%02x", hci_cmd_buf[i]); + #if HCI_TRACE + printf("< [% 8d] %02x", mp_hal_ticks_ms(), mp_bluetooth_hci_cmd_buf[0]); + for (size_t i = 1; i < len; ++i) { + printf(":%02x", mp_bluetooth_hci_cmd_buf[i]); } printf("\n"); #endif @@ -77,13 +80,21 @@ int hal_uart_close(uint32_t port) { return 0; // success } -void mp_bluetooth_nimble_hci_uart_process(void) { +void mp_bluetooth_nimble_hci_uart_process(bool run_events) { bool host_wake = mp_bluetooth_hci_controller_woken(); int chr; while ((chr = mp_bluetooth_hci_uart_readchar()) >= 0) { - // printf("UART RX: %02x\n", data); + #if HCI_TRACE + printf("> %02x (%d)\n", chr); + #endif hal_uart_rx_cb(hal_uart_rx_arg, chr); + + // Incoming data may result in events being enqueued. If we're in + // scheduler context then we can run those events immediately. + if (run_events) { + mp_bluetooth_nimble_os_eventq_run_all(); + } } if (host_wake) { diff --git a/extmod/nimble/hal/hal_uart.h b/extmod/nimble/hal/hal_uart.h index 1ff1c17436dae..647e8ab4772e2 100644 --- a/extmod/nimble/hal/hal_uart.h +++ b/extmod/nimble/hal/hal_uart.h @@ -43,6 +43,6 @@ void hal_uart_start_tx(uint32_t port); int hal_uart_close(uint32_t port); // --- Called by the MicroPython port when UART data is available ------------- -void mp_bluetooth_nimble_hci_uart_process(void); +void mp_bluetooth_nimble_hci_uart_process(bool run_events); #endif // MICROPY_INCLUDED_EXTMOD_NIMBLE_HAL_HAL_UART_H diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 7ee6ae8677e8b..c961aee3262c9 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -382,6 +382,7 @@ int mp_bluetooth_init(void) { mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC; // Initialise NimBLE memory and data structures. + DEBUG_printf("mp_bluetooth_init: nimble_port_init\n"); nimble_port_init(); // Make sure that the HCI UART and event handling task is running. @@ -402,6 +403,8 @@ int mp_bluetooth_init(void) { return MP_ETIMEDOUT; } + DEBUG_printf("mp_bluetooth_init: starting services\n"); + // By default, just register the default gap/gatt service. ble_svc_gap_init(); ble_svc_gatt_init(); @@ -417,7 +420,7 @@ int mp_bluetooth_init(void) { } void mp_bluetooth_deinit(void) { - DEBUG_printf("mp_bluetooth_deinit\n"); + DEBUG_printf("mp_bluetooth_deinit %d\n", mp_bluetooth_nimble_ble_state); if (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { return; } diff --git a/extmod/nimble/nimble.mk b/extmod/nimble/nimble.mk index fbd031b3e3e16..00a244d6eac77 100644 --- a/extmod/nimble/nimble.mk +++ b/extmod/nimble/nimble.mk @@ -17,6 +17,13 @@ CFLAGS_MOD += -DMICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY=$(MICROPY_BLUETOOTH_NIMBL ifeq ($(MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY),0) +# On all ports where we provide the full implementation (i.e. not just +# bindings like on ESP32), then we don't need to use the ringbuffer. In this +# case, all NimBLE events are run by the MicroPython scheduler. On Unix, the +# scheduler is also responsible for polling the UART, whereas on STM32 the +# UART is also polled by the RX IRQ. +CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS=1 + NIMBLE_LIB_DIR = lib/mynewt-nimble LIB_SRC_C += $(addprefix $(NIMBLE_LIB_DIR)/, \ diff --git a/extmod/nimble/nimble/nimble_npl_os.c b/extmod/nimble/nimble/nimble_npl_os.c index 2ec012940f763..b68957fabf385 100644 --- a/extmod/nimble/nimble/nimble_npl_os.c +++ b/extmod/nimble/nimble/nimble_npl_os.c @@ -179,63 +179,100 @@ int nimble_sprintf(char *str, const char *fmt, ...) { struct ble_npl_eventq *global_eventq = NULL; +// This must not be called recursively or concurrently with the UART handler. void mp_bluetooth_nimble_os_eventq_run_all(void) { - for (struct ble_npl_eventq *evq = global_eventq; evq != NULL; evq = evq->nextq) { - int n = 0; - while (evq->head != NULL && mp_bluetooth_nimble_ble_state > MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { - struct ble_npl_event *ev = evq->head; - evq->head = ev->next; - if (ev->next) { - ev->next->prev = NULL; - ev->next = NULL; - } - ev->prev = NULL; - DEBUG_EVENT_printf("event_run(%p)\n", ev); - ev->fn(ev); - DEBUG_EVENT_printf("event_run(%p) done\n", ev); - - if (++n > 3) { - // Limit to running 3 tasks per queue. - // Some tasks (such as reset) can enqueue themselves - // making this an infinite loop (while in PENDSV). + if (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { + return; + } + + // Keep running while there are pending events. + while (true) { + struct ble_npl_event *ev = NULL; + + os_sr_t sr; + OS_ENTER_CRITICAL(sr); + // Search all queues for an event. + for (struct ble_npl_eventq *evq = global_eventq; evq != NULL; evq = evq->nextq) { + ev = evq->head; + if (ev) { + // Remove this event from the queue. + evq->head = ev->next; + if (ev->next) { + ev->next->prev = NULL; + ev->next = NULL; + } + ev->prev = NULL; + + ev->pending = false; + + // Stop searching and execute this event. break; } } + OS_EXIT_CRITICAL(sr); + + if (!ev) { + break; + } + + // Run the event handler. + DEBUG_EVENT_printf("event_run(%p)\n", ev); + ev->fn(ev); + DEBUG_EVENT_printf("event_run(%p) done\n", ev); + + if (ev->pending) { + // If this event has been re-enqueued while it was running, then + // stop running further events. This prevents an infinite loop + // where the reset event re-enqueues itself on HCI timeout. + break; + } } } void ble_npl_eventq_init(struct ble_npl_eventq *evq) { DEBUG_EVENT_printf("ble_npl_eventq_init(%p)\n", evq); + os_sr_t sr; + OS_ENTER_CRITICAL(sr); evq->head = NULL; struct ble_npl_eventq **evq2; for (evq2 = &global_eventq; *evq2 != NULL; evq2 = &(*evq2)->nextq) { } *evq2 = evq; evq->nextq = NULL; + OS_EXIT_CRITICAL(sr); } void ble_npl_eventq_put(struct ble_npl_eventq *evq, struct ble_npl_event *ev) { DEBUG_EVENT_printf("ble_npl_eventq_put(%p, %p (%p, %p))\n", evq, ev, ev->fn, ev->arg); + os_sr_t sr; + OS_ENTER_CRITICAL(sr); ev->next = NULL; + ev->pending = true; if (evq->head == NULL) { + // Empty list, make this the first item. evq->head = ev; ev->prev = NULL; } else { - struct ble_npl_event *ev2 = evq->head; + // Find the tail of this list. + struct ble_npl_event *tail = evq->head; while (true) { - if (ev2 == ev) { + if (tail == ev) { DEBUG_EVENT_printf(" --> already in queue\n"); - return; + // Already in the list (e.g. a fragmented ACL will enqueue an + // event to process it for each fragment). + break; } - if (ev2->next == NULL) { + if (tail->next == NULL) { + // Found the end of the list, add this event as the tail. + tail->next = ev; + ev->prev = tail; break; } - DEBUG_EVENT_printf(" --> %p\n", ev2->next); - ev2 = ev2->next; + DEBUG_EVENT_printf(" --> %p\n", tail->next); + tail = tail->next; } - ev2->next = ev; - ev->prev = ev2; } + OS_EXIT_CRITICAL(sr); } void ble_npl_event_init(struct ble_npl_event *ev, ble_npl_event_fn *fn, void *arg) { @@ -243,6 +280,7 @@ void ble_npl_event_init(struct ble_npl_event *ev, ble_npl_event_fn *fn, void *ar ev->fn = fn; ev->arg = arg; ev->next = NULL; + ev->pending = false; } void *ble_npl_event_get_arg(struct ble_npl_event *ev) { @@ -258,44 +296,17 @@ void ble_npl_event_set_arg(struct ble_npl_event *ev, void *arg) { /******************************************************************************/ // MUTEX -// This is what MICROPY_BEGIN_ATOMIC_SECTION returns on Unix (i.e. we don't -// need to preserve the atomic state to unlock). -#define ATOMIC_STATE_MUTEX_NOT_HELD 0xffffffff - ble_npl_error_t ble_npl_mutex_init(struct ble_npl_mutex *mu) { DEBUG_MUTEX_printf("ble_npl_mutex_init(%p)\n", mu); mu->locked = 0; - mu->atomic_state = ATOMIC_STATE_MUTEX_NOT_HELD; return BLE_NPL_OK; } ble_npl_error_t ble_npl_mutex_pend(struct ble_npl_mutex *mu, ble_npl_time_t timeout) { - DEBUG_MUTEX_printf("ble_npl_mutex_pend(%p, %u) locked=%u irq=%d\n", mu, (uint)timeout, (uint)mu->locked); + DEBUG_MUTEX_printf("ble_npl_mutex_pend(%p, %u) locked=%u\n", mu, (uint)timeout, (uint)mu->locked); - // This is a recursive mutex which we implement on top of the IRQ priority - // scheme. Unfortunately we have a single piece of global storage, where - // enter/exit critical needs an "atomic state". - - // There are two different acquirers, either running in a VM thread (i.e. - // a direct Python call into NimBLE), or in the NimBLE task (i.e. polling - // or UART RX). - - // On STM32 the NimBLE task runs in PENDSV, so cannot be interrupted by a VM thread. - // Therefore we only need to ensure that a VM thread that acquires a currently-unlocked mutex - // now raises the priority (thus preventing context switches to other VM threads and - // the PENDSV irq). If the mutex is already locked, then it must have been acquired - // by us. - - // On Unix, the critical section is completely recursive and doesn't require us to manage - // state so we just acquire and release every time. - - // TODO: The "volatile" on locked/atomic_state isn't enough to protect against memory re-ordering. - - // First acquirer of this mutex always enters the critical section, unless - // we're on Unix where it happens every time. - if (mu->atomic_state == ATOMIC_STATE_MUTEX_NOT_HELD) { - mu->atomic_state = mp_bluetooth_nimble_hci_uart_enter_critical(); - } + // All NimBLE code is executed by the scheduler (and is therefore + // implicitly mutexed) so this mutex implementation is a no-op. ++mu->locked; @@ -303,17 +314,11 @@ ble_npl_error_t ble_npl_mutex_pend(struct ble_npl_mutex *mu, ble_npl_time_t time } ble_npl_error_t ble_npl_mutex_release(struct ble_npl_mutex *mu) { - DEBUG_MUTEX_printf("ble_npl_mutex_release(%p) locked=%u irq=%d\n", mu, (uint)mu->locked); + DEBUG_MUTEX_printf("ble_npl_mutex_release(%p) locked=%u\n", mu, (uint)mu->locked); assert(mu->locked > 0); --mu->locked; - // Only exit the critical section for the final release, unless we're on Unix. - if (mu->locked == 0 || mu->atomic_state == ATOMIC_STATE_MUTEX_NOT_HELD) { - mp_bluetooth_nimble_hci_uart_exit_critical(mu->atomic_state); - mu->atomic_state = ATOMIC_STATE_MUTEX_NOT_HELD; - } - return BLE_NPL_OK; } @@ -329,30 +334,19 @@ ble_npl_error_t ble_npl_sem_init(struct ble_npl_sem *sem, uint16_t tokens) { ble_npl_error_t ble_npl_sem_pend(struct ble_npl_sem *sem, ble_npl_time_t timeout) { DEBUG_SEM_printf("ble_npl_sem_pend(%p, %u) count=%u\n", sem, (uint)timeout, (uint)sem->count); - // This is called by NimBLE to synchronously wait for an HCI ACK. The - // corresponding ble_npl_sem_release is called directly by the UART rx - // handler (i.e. hal_uart_rx_cb in extmod/nimble/hal/hal_uart.c). - - // So this implementation just polls the UART until either the semaphore - // is released, or the timeout occurs. + // This is only called by NimBLE in ble_hs_hci_cmd_tx to synchronously + // wait for an HCI ACK. The corresponding ble_npl_sem_release is called + // directly by the UART rx handler (i.e. hal_uart_rx_cb in + // extmod/nimble/hal/hal_uart.c). So this loop needs to run only the HCI + // UART processing but not run any events. if (sem->count == 0) { uint32_t t0 = mp_hal_ticks_ms(); while (sem->count == 0 && mp_hal_ticks_ms() - t0 < timeout) { - // This can be called either from code running in NimBLE's "task" - // (i.e. PENDSV) or directly by application code, so for the - // latter case, prevent the "task" from running while we poll the - // UART directly. - MICROPY_PY_BLUETOOTH_ENTER - mp_bluetooth_nimble_hci_uart_process(); - MICROPY_PY_BLUETOOTH_EXIT - if (sem->count != 0) { break; } - // Because we're polling, might as well wait for a UART IRQ indicating - // more data available. mp_bluetooth_nimble_hci_uart_wfi(); } @@ -384,6 +378,8 @@ uint16_t ble_npl_sem_get_count(struct ble_npl_sem *sem) { static struct ble_npl_callout *global_callout = NULL; void mp_bluetooth_nimble_os_callout_process(void) { + os_sr_t sr; + OS_ENTER_CRITICAL(sr); uint32_t tnow = mp_hal_ticks_ms(); for (struct ble_npl_callout *c = global_callout; c != NULL; c = c->nextc) { if (!c->active) { @@ -393,17 +389,24 @@ void mp_bluetooth_nimble_os_callout_process(void) { DEBUG_CALLOUT_printf("callout_run(%p) tnow=%u ticks=%u evq=%p\n", c, (uint)tnow, (uint)c->ticks, c->evq); c->active = false; if (c->evq) { + // Enqueue this callout for execution in the event queue. ble_npl_eventq_put(c->evq, &c->ev); } else { + // Execute this callout directly. + OS_EXIT_CRITICAL(sr); c->ev.fn(&c->ev); + OS_ENTER_CRITICAL(sr); } DEBUG_CALLOUT_printf("callout_run(%p) done\n", c); } } + OS_EXIT_CRITICAL(sr); } void ble_npl_callout_init(struct ble_npl_callout *c, struct ble_npl_eventq *evq, ble_npl_event_fn *ev_cb, void *ev_arg) { DEBUG_CALLOUT_printf("ble_npl_callout_init(%p, %p, %p, %p)\n", c, evq, ev_cb, ev_arg); + os_sr_t sr; + OS_ENTER_CRITICAL(sr); c->active = false; c->ticks = 0; c->evq = evq; @@ -413,17 +416,22 @@ void ble_npl_callout_init(struct ble_npl_callout *c, struct ble_npl_eventq *evq, for (c2 = &global_callout; *c2 != NULL; c2 = &(*c2)->nextc) { if (c == *c2) { // callout already in linked list so don't link it in again + OS_EXIT_CRITICAL(sr); return; } } *c2 = c; c->nextc = NULL; + OS_EXIT_CRITICAL(sr); } ble_npl_error_t ble_npl_callout_reset(struct ble_npl_callout *c, ble_npl_time_t ticks) { DEBUG_CALLOUT_printf("ble_npl_callout_reset(%p, %u) tnow=%u\n", c, (uint)ticks, (uint)mp_hal_ticks_ms()); + os_sr_t sr; + OS_ENTER_CRITICAL(sr); c->active = true; c->ticks = ble_npl_time_get() + ticks; + OS_EXIT_CRITICAL(sr); return BLE_NPL_OK; } @@ -493,23 +501,20 @@ void ble_npl_time_delay(ble_npl_time_t ticks) { // CRITICAL // This is used anywhere NimBLE modifies global data structures. -// We need to protect between: -// - A MicroPython VM thread. -// - The NimBLE "task" (e.g. PENDSV on STM32, pthread on Unix). -// On STM32, by disabling PENDSV, we ensure that either: -// - If we're in the NimBLE task, we're exclusive anyway. -// - If we're in a VM thread, we can't be interrupted by the NimBLE task, or switched to another thread. -// On Unix, there's a global mutex. -// TODO: Both ports currently use MICROPY_PY_BLUETOOTH_ENTER in their implementation, -// maybe this doesn't need to be port-specific? +// Currently all NimBLE code is invoked by the scheduler so there should be no +// races, so on STM32 MICROPY_PY_BLUETOOTH_ENTER/MICROPY_PY_BLUETOOTH_EXIT are +// no-ops. However, in the future we may wish to make HCI UART processing +// happen asynchronously (e.g. on RX IRQ), so the port can implement these +// macros accordingly. uint32_t ble_npl_hw_enter_critical(void) { DEBUG_CRIT_printf("ble_npl_hw_enter_critical()\n"); - return mp_bluetooth_nimble_hci_uart_enter_critical(); + MICROPY_PY_BLUETOOTH_ENTER + return atomic_state; } -void ble_npl_hw_exit_critical(uint32_t ctx) { - DEBUG_CRIT_printf("ble_npl_hw_exit_critical(%u)\n", (uint)ctx); - mp_bluetooth_nimble_hci_uart_exit_critical(ctx); +void ble_npl_hw_exit_critical(uint32_t atomic_state) { + MICROPY_PY_BLUETOOTH_EXIT + DEBUG_CRIT_printf("ble_npl_hw_exit_critical(%u)\n", (uint)atomic_state); } diff --git a/extmod/nimble/nimble/nimble_npl_os.h b/extmod/nimble/nimble/nimble_npl_os.h index 3ef07aa9ccd12..bfabe56e895c0 100644 --- a/extmod/nimble/nimble/nimble_npl_os.h +++ b/extmod/nimble/nimble/nimble_npl_os.h @@ -42,6 +42,7 @@ typedef int32_t ble_npl_stime_t; struct ble_npl_event { ble_npl_event_fn *fn; void *arg; + bool pending; struct ble_npl_event *prev; struct ble_npl_event *next; }; @@ -61,7 +62,6 @@ struct ble_npl_callout { struct ble_npl_mutex { volatile uint8_t locked; - volatile uint32_t atomic_state; }; struct ble_npl_sem { @@ -76,7 +76,5 @@ void mp_bluetooth_nimble_os_callout_process(void); // --- Must be provided by the MicroPython port ------------------------------- void mp_bluetooth_nimble_hci_uart_wfi(void); -uint32_t mp_bluetooth_nimble_hci_uart_enter_critical(void); -void mp_bluetooth_nimble_hci_uart_exit_critical(uint32_t atomic_state); #endif // MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NPL_OS_H diff --git a/ports/stm32/main.c b/ports/stm32/main.c index db8222479b0f6..d00c2ec713f61 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -433,8 +433,8 @@ void stm32_main(uint32_t reset_mode) { systick_enable_dispatch(SYSTICK_DISPATCH_LWIP, mod_network_lwip_poll_wrapper); #endif #if MICROPY_PY_BLUETOOTH - extern void mp_bluetooth_hci_poll_wrapper(uint32_t ticks_ms); - systick_enable_dispatch(SYSTICK_DISPATCH_BLUETOOTH_HCI, mp_bluetooth_hci_poll_wrapper); + extern void mp_bluetooth_hci_systick(uint32_t ticks_ms); + systick_enable_dispatch(SYSTICK_DISPATCH_BLUETOOTH_HCI, mp_bluetooth_hci_systick); #endif #if MICROPY_PY_NETWORK_CYW43 diff --git a/ports/stm32/mpbthciport.c b/ports/stm32/mpbthciport.c index a5977ff12c210..ee9f4e31eb2ba 100644 --- a/ports/stm32/mpbthciport.c +++ b/ports/stm32/mpbthciport.c @@ -27,6 +27,7 @@ #include "py/runtime.h" #include "py/mphal.h" #include "extmod/mpbthci.h" +#include "extmod/modbluetooth.h" #include "systick.h" #include "pendsv.h" #include "lib/utils/mpirq.h" @@ -35,23 +36,58 @@ #if MICROPY_PY_BLUETOOTH -#define DEBUG_printf(...) // printf(__VA_ARGS__) +#define DEBUG_printf(...) // printf("mpbthciport.c: " __VA_ARGS__) uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; // Must be provided by the stack bindings (e.g. mpnimbleport.c or mpbtstackport.c). +// Request new data from the uart and pass to the stack, and run pending events/callouts. extern void mp_bluetooth_hci_poll(void); // Hook for pendsv poller to run this periodically every 128ms #define BLUETOOTH_HCI_TICK(tick) (((tick) & ~(SYSTICK_DISPATCH_NUM_SLOTS - 1) & 0x7f) == 0) +#if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS + +// For synchronous mode, we run all BLE stack code inside a scheduled task. +// This task is scheduled periodically (every 128ms) via SysTick, or +// immediately on HCI UART RXIDLE. + +// Prevent double-enqueuing of the scheduled task. +STATIC volatile bool events_task_is_scheduled = false; + +STATIC mp_obj_t run_events_scheduled_task(mp_obj_t none_in) { + (void)none_in; + events_task_is_scheduled = false; + // This will process all buffered HCI UART data, and run any callouts or events. + mp_bluetooth_hci_poll(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(run_events_scheduled_task_obj, run_events_scheduled_task); + +// Called periodically (systick) or directly (e.g. UART RX IRQ) in order to +// request that processing happens ASAP in the scheduler. +void mp_bluetooth_hci_systick(uint32_t ticks_ms) { + if (events_task_is_scheduled) { + return; + } + + if (ticks_ms == 0 || BLUETOOTH_HCI_TICK(ticks_ms)) { + events_task_is_scheduled = mp_sched_schedule(MP_OBJ_FROM_PTR(&run_events_scheduled_task_obj), mp_const_none); + } +} + +#else // !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS + // Called periodically (systick) or directly (e.g. uart irq). -void mp_bluetooth_hci_poll_wrapper(uint32_t ticks_ms) { +void mp_bluetooth_hci_systick(uint32_t ticks_ms) { if (ticks_ms == 0 || BLUETOOTH_HCI_TICK(ticks_ms)) { pendsv_schedule_dispatch(PENDSV_DISPATCH_BLUETOOTH_HCI, mp_bluetooth_hci_poll); } } +#endif + #if defined(STM32WB) /******************************************************************************/ @@ -67,13 +103,23 @@ STATIC uint8_t hci_uart_rx_buf_data[256]; int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { (void)port; (void)baudrate; + + DEBUG_printf("mp_bluetooth_hci_uart_init (stm32 rfcore)\n"); + + #if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS + events_task_is_scheduled = false; + #endif + rfcore_ble_init(); hci_uart_rx_buf_cur = 0; hci_uart_rx_buf_len = 0; + return 0; } int mp_bluetooth_hci_uart_deinit(void) { + DEBUG_printf("mp_bluetooth_hci_uart_deinit (stm32 rfcore)\n"); + return 0; } @@ -125,12 +171,12 @@ int mp_bluetooth_hci_uart_readchar(void) { pyb_uart_obj_t mp_bluetooth_hci_uart_obj; mp_irq_obj_t mp_bluetooth_hci_uart_irq_obj; -static uint8_t hci_uart_rxbuf[512]; +static uint8_t hci_uart_rxbuf[768]; mp_obj_t mp_uart_interrupt(mp_obj_t self_in) { - // DEBUG_printf("mp_uart_interrupt\n"); - // New HCI data, schedule mp_bluetooth_hci_poll via PENDSV to make the stack handle it. - mp_bluetooth_hci_poll_wrapper(0); + // Queue up the scheduler to run the HCI UART and event processing ASAP. + mp_bluetooth_hci_systick(0); + return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_1(mp_uart_interrupt_obj, mp_uart_interrupt); @@ -138,6 +184,10 @@ MP_DEFINE_CONST_FUN_OBJ_1(mp_uart_interrupt_obj, mp_uart_interrupt); int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { DEBUG_printf("mp_bluetooth_hci_uart_init (stm32)\n"); + #if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS + events_task_is_scheduled = false; + #endif + // bits (8), stop (1), parity (none) and flow (rts/cts) are assumed to match MYNEWT_VAL_BLE_HCI_UART_ constants in syscfg.h. mp_bluetooth_hci_uart_obj.base.type = &pyb_uart_type; mp_bluetooth_hci_uart_obj.uart_id = port; @@ -147,7 +197,7 @@ int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { mp_bluetooth_hci_uart_obj.timeout_char = 200; MP_STATE_PORT(pyb_uart_obj_all)[mp_bluetooth_hci_uart_obj.uart_id - 1] = &mp_bluetooth_hci_uart_obj; - // This also initialises the UART. + // This also initialises the UART and adds the RXIDLE IRQ handler. mp_bluetooth_hci_uart_set_baudrate(baudrate); return 0; @@ -155,6 +205,7 @@ int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { int mp_bluetooth_hci_uart_deinit(void) { DEBUG_printf("mp_bluetooth_hci_uart_deinit (stm32)\n"); + // TODO: deinit mp_bluetooth_hci_uart_obj return 0; diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 5f8e7ec2de1c1..b6906ef998c2e 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -420,9 +420,16 @@ static inline mp_uint_t disable_irq(void) { #define MICROPY_PY_LWIP_REENTER MICROPY_PY_PENDSV_REENTER #define MICROPY_PY_LWIP_EXIT MICROPY_PY_PENDSV_EXIT -// Prevent the "Bluetooth task" from running (either NimBLE or btstack). +#if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS +// Bluetooth code only runs in the scheduler, no locking/mutex required. +#define MICROPY_PY_BLUETOOTH_ENTER uint32_t atomic_state = 0; +#define MICROPY_PY_BLUETOOTH_EXIT (void)atomic_state; +#else +// When async events are enabled, need to prevent PendSV execution racing with +// scheduler execution. #define MICROPY_PY_BLUETOOTH_ENTER MICROPY_PY_PENDSV_ENTER #define MICROPY_PY_BLUETOOTH_EXIT MICROPY_PY_PENDSV_EXIT +#endif // We need an implementation of the log2 function which is not a macro #define MP_NEED_LOG2 (1) diff --git a/ports/stm32/mpnimbleport.c b/ports/stm32/mpnimbleport.c index 1d7c095139cde..0ba76fb277ea2 100644 --- a/ports/stm32/mpnimbleport.c +++ b/ports/stm32/mpnimbleport.c @@ -31,24 +31,29 @@ #if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE +#define DEBUG_printf(...) // printf("mpnimbleport.c: " __VA_ARGS__) + #include "host/ble_hs.h" #include "nimble/nimble_npl.h" #include "extmod/mpbthci.h" +#include "extmod/modbluetooth.h" #include "extmod/nimble/modbluetooth_nimble.h" #include "extmod/nimble/hal/hal_uart.h" -// This implements the Nimble "background task". It's called at PENDSV -// priority, either every 128ms or whenever there's UART data available. -// Because it's called via PENDSV, you can implicitly consider that it -// is surrounded by MICROPY_PY_BLUETOOTH_ENTER / MICROPY_PY_BLUETOOTH_EXIT. +// Get any pending data from the UART and send it to NimBLE's HCI buffers. +// Any further processing by NimBLE will be run via its event queue. void mp_bluetooth_hci_poll(void) { if (mp_bluetooth_nimble_ble_state >= MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC) { - // Ask NimBLE to process UART data. - mp_bluetooth_nimble_hci_uart_process(); + // DEBUG_printf("mp_bluetooth_hci_poll_uart %d\n", mp_bluetooth_nimble_ble_state); - // Run pending background operations and events, but only after HCI sync. + // Run any timers. mp_bluetooth_nimble_os_callout_process(); + + // Process incoming UART data, and run events as they are generated. + mp_bluetooth_nimble_hci_uart_process(true); + + // Run any remaining events (e.g. if there was no UART data). mp_bluetooth_nimble_os_eventq_run_all(); } } @@ -57,15 +62,10 @@ void mp_bluetooth_hci_poll(void) { void mp_bluetooth_nimble_hci_uart_wfi(void) { __WFI(); -} - -uint32_t mp_bluetooth_nimble_hci_uart_enter_critical(void) { - MICROPY_PY_BLUETOOTH_ENTER - return atomic_state; -} -void mp_bluetooth_nimble_hci_uart_exit_critical(uint32_t atomic_state) { - MICROPY_PY_BLUETOOTH_EXIT + // This is called while NimBLE is waiting in ble_npl_sem_pend, i.e. waiting for an HCI ACK. + // Do not need to run events here (it must not invoke Python code), only processing incoming HCI data. + mp_bluetooth_nimble_hci_uart_process(false); } #endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE diff --git a/ports/stm32/pendsv.h b/ports/stm32/pendsv.h index 585f81e8bd441..aa8f90e3e428f 100644 --- a/ports/stm32/pendsv.h +++ b/ports/stm32/pendsv.h @@ -34,7 +34,7 @@ enum { PENDSV_DISPATCH_CYW43, #endif #endif - #if MICROPY_PY_BLUETOOTH + #if MICROPY_PY_BLUETOOTH && !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS PENDSV_DISPATCH_BLUETOOTH_HCI, #endif PENDSV_DISPATCH_MAX diff --git a/ports/stm32/rfcore.c b/ports/stm32/rfcore.c index 1fc0c9531d03b..08338fcdc25c5 100644 --- a/ports/stm32/rfcore.c +++ b/ports/stm32/rfcore.c @@ -634,9 +634,9 @@ void IPCC_C1_RX_IRQHandler(void) { LL_C1_IPCC_ClearFlag_CHx(IPCC, IPCC_CH_BLE); - // Schedule PENDSV to process incoming HCI payload. - extern void mp_bluetooth_hci_poll_wrapper(uint32_t ticks_ms); - mp_bluetooth_hci_poll_wrapper(0); + // Queue up the scheduler to process UART data and run events. + extern void mp_bluetooth_hci_systick(uint32_t ticks_ms); + mp_bluetooth_hci_systick(0); } IRQ_EXIT(IPCC_C1_RX_IRQn); diff --git a/ports/unix/mpbthciport.c b/ports/unix/mpbthciport.c index 316a8831fe97b..14afbebcd7ec5 100644 --- a/ports/unix/mpbthciport.c +++ b/ports/unix/mpbthciport.c @@ -50,22 +50,67 @@ uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; +STATIC int uart_fd = -1; + // Must be provided by the stack bindings (e.g. mpnimbleport.c or mpbtstackport.c). extern bool mp_bluetooth_hci_poll(void); -STATIC const useconds_t UART_POLL_INTERVAL_US = 1000; +#if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS -STATIC int uart_fd = -1; +// For synchronous mode, we run all BLE stack code inside a scheduled task. +// This task is scheduled periodically (every 1ms) by a background thread. + +// Allows the stack to tell us that we should stop trying to schedule. +extern bool mp_bluetooth_hci_active(void); + +// Prevent double-enqueuing of the scheduled task. +STATIC volatile bool events_task_is_scheduled = false; + +STATIC mp_obj_t run_events_scheduled_task(mp_obj_t none_in) { + (void)none_in; + MICROPY_PY_BLUETOOTH_ENTER + events_task_is_scheduled = false; + MICROPY_PY_BLUETOOTH_EXIT + mp_bluetooth_hci_poll(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(run_events_scheduled_task_obj, run_events_scheduled_task); + +#endif // MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS + +STATIC const useconds_t UART_POLL_INTERVAL_US = 1000; STATIC pthread_t hci_poll_thread_id; STATIC void *hci_poll_thread(void *arg) { (void)arg; + DEBUG_printf("hci_poll_thread: starting\n"); + + #if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS + + events_task_is_scheduled = false; + + while (mp_bluetooth_hci_active()) { + MICROPY_PY_BLUETOOTH_ENTER + if (!events_task_is_scheduled) { + events_task_is_scheduled = mp_sched_schedule(MP_OBJ_FROM_PTR(&run_events_scheduled_task_obj), mp_const_none); + } + MICROPY_PY_BLUETOOTH_EXIT + usleep(UART_POLL_INTERVAL_US); + } + + #else + + // In asynchronous (i.e. ringbuffer) mode, we run the BLE stack directly from the thread. // This will return false when the stack is shutdown. while (mp_bluetooth_hci_poll()) { usleep(UART_POLL_INTERVAL_US); } + #endif + + DEBUG_printf("hci_poll_thread: stopped\n"); + return NULL; } @@ -122,6 +167,11 @@ int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { DEBUG_printf("mp_bluetooth_hci_uart_init (unix)\n"); + if (uart_fd != -1) { + DEBUG_printf("mp_bluetooth_hci_uart_init: already active\n"); + return 0; + } + char uart_device_name[256] = "/dev/ttyUSB0"; char *path = getenv("MICROPYBTUART"); diff --git a/ports/unix/mpnimbleport.c b/ports/unix/mpnimbleport.c index 8961910098c42..29f558f74db51 100644 --- a/ports/unix/mpnimbleport.c +++ b/ports/unix/mpnimbleport.c @@ -47,38 +47,28 @@ bool mp_bluetooth_hci_poll(void) { } if (mp_bluetooth_nimble_ble_state >= MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC) { + // Run any timers. + mp_bluetooth_nimble_os_callout_process(); - // Pretend like we're running in IRQ context (i.e. other things can't be running at the same time). - mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); - - // Ask NimBLE to process UART data. - mp_bluetooth_nimble_hci_uart_process(); + // Process incoming UART data, and run events as they are generated. + mp_bluetooth_nimble_hci_uart_process(true); - // Run pending background operations and events, but only after HCI sync. - mp_bluetooth_nimble_os_callout_process(); + // Run any remaining events (e.g. if there was no UART data). mp_bluetooth_nimble_os_eventq_run_all(); - - MICROPY_END_ATOMIC_SECTION(atomic_state); } return true; } -// Extra port-specific helpers. -void mp_bluetooth_nimble_hci_uart_wfi(void) { - // DEBUG_printf("mp_bluetooth_nimble_hci_uart_wfi\n"); - // TODO: this should do a select() on the uart_fd. -} - -uint32_t mp_bluetooth_nimble_hci_uart_enter_critical(void) { - // DEBUG_printf("mp_bluetooth_nimble_hci_uart_enter_critical\n"); - MICROPY_PY_BLUETOOTH_ENTER - return atomic_state; // Always 0xffffffff +bool mp_bluetooth_hci_active(void) { + return mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF; } -void mp_bluetooth_nimble_hci_uart_exit_critical(uint32_t atomic_state) { - MICROPY_PY_BLUETOOTH_EXIT - // DEBUG_printf("mp_bluetooth_nimble_hci_uart_exit_critical\n"); +// Extra port-specific helpers. +void mp_bluetooth_nimble_hci_uart_wfi(void) { + // This is called while NimBLE is waiting in ble_npl_sem_pend, i.e. waiting for an HCI ACK. + // Do not need to run events here, only processing incoming HCI data. + mp_bluetooth_nimble_hci_uart_process(false); } #endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE From 7e75245d549d01ac4f01ee615c193c00cf3269a7 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Thu, 12 Nov 2020 23:05:47 +1100 Subject: [PATCH 078/179] tests/multi_bluetooth: Change dict index-and-del to pop, to clear event. Signed-off-by: Jim Mussared --- tests/multi_bluetooth/ble_characteristic.py | 4 +--- tests/multi_bluetooth/ble_gap_connect.py | 4 +--- tests/multi_bluetooth/ble_gap_device_name.py | 4 +--- tests/multi_bluetooth/ble_gatt_data_transfer.py | 4 +--- tests/multi_bluetooth/ble_gattc_discover_services.py | 4 +--- tests/multi_bluetooth/ble_mtu.py | 4 +--- 6 files changed, 6 insertions(+), 18 deletions(-) diff --git a/tests/multi_bluetooth/ble_characteristic.py b/tests/multi_bluetooth/ble_characteristic.py index 0f22daff88f0a..327822a01ee15 100644 --- a/tests/multi_bluetooth/ble_characteristic.py +++ b/tests/multi_bluetooth/ble_characteristic.py @@ -77,9 +77,7 @@ def wait_for_event(event, timeout_ms): t0 = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: if event in waiting_events: - result = waiting_events[event] - del waiting_events[event] - return result + return waiting_events.pop(event) machine.idle() raise ValueError("Timeout waiting for {}".format(event)) diff --git a/tests/multi_bluetooth/ble_gap_connect.py b/tests/multi_bluetooth/ble_gap_connect.py index 2c1d2cbbc55dc..95d1be7baa2e4 100644 --- a/tests/multi_bluetooth/ble_gap_connect.py +++ b/tests/multi_bluetooth/ble_gap_connect.py @@ -33,9 +33,7 @@ def wait_for_event(event, timeout_ms): t0 = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: if event in waiting_events: - result = waiting_events[event] - del waiting_events[event] - return result + return waiting_events.pop(event) machine.idle() raise ValueError("Timeout waiting for {}".format(event)) diff --git a/tests/multi_bluetooth/ble_gap_device_name.py b/tests/multi_bluetooth/ble_gap_device_name.py index e7202170bd8a4..556068114bbbc 100644 --- a/tests/multi_bluetooth/ble_gap_device_name.py +++ b/tests/multi_bluetooth/ble_gap_device_name.py @@ -49,9 +49,7 @@ def wait_for_event(event, timeout_ms): t0 = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: if event in waiting_events: - result = waiting_events[event] - del waiting_events[event] - return result + return waiting_events.pop(event) machine.idle() raise ValueError("Timeout waiting for {}".format(event)) diff --git a/tests/multi_bluetooth/ble_gatt_data_transfer.py b/tests/multi_bluetooth/ble_gatt_data_transfer.py index e8249b3da8962..861cb49fc34f2 100644 --- a/tests/multi_bluetooth/ble_gatt_data_transfer.py +++ b/tests/multi_bluetooth/ble_gatt_data_transfer.py @@ -76,9 +76,7 @@ def wait_for_event(event, timeout_ms): t0 = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: if event in waiting_events: - result = waiting_events[event] - del waiting_events[event] - return result + return waiting_events.pop(event) machine.idle() raise ValueError("Timeout waiting for {}".format(event)) diff --git a/tests/multi_bluetooth/ble_gattc_discover_services.py b/tests/multi_bluetooth/ble_gattc_discover_services.py index 00bb94bba0ba8..f6ee5fa00e1fa 100644 --- a/tests/multi_bluetooth/ble_gattc_discover_services.py +++ b/tests/multi_bluetooth/ble_gattc_discover_services.py @@ -55,9 +55,7 @@ def wait_for_event(event, timeout_ms): t0 = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: if event in waiting_events: - result = waiting_events[event] - del waiting_events[event] - return result + return waiting_events.pop(event) machine.idle() raise ValueError("Timeout waiting for {}".format(event)) diff --git a/tests/multi_bluetooth/ble_mtu.py b/tests/multi_bluetooth/ble_mtu.py index 73c77e2460c65..f202ec764de9b 100644 --- a/tests/multi_bluetooth/ble_mtu.py +++ b/tests/multi_bluetooth/ble_mtu.py @@ -87,9 +87,7 @@ def wait_for_event(event, timeout_ms): t0 = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: if event in waiting_events: - result = waiting_events[event] - del waiting_events[event] - return result + return waiting_events.pop(event) machine.idle() raise ValueError("Timeout waiting for {}".format(event)) From efc0800132f7c85a55718ba71f5d2729d244f85f Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Wed, 4 Nov 2020 12:54:46 +1100 Subject: [PATCH 079/179] tests/multi_bluetooth: Add a test for WB55 concurrent flash access. This test currently passes on Unix/PYBD, but fails on WB55 because it lacks synchronisation of the internal flash. Signed-off-by: Jim Mussared --- .../multi_bluetooth/stress_log_filesystem.py | 188 ++++++++++++++++++ .../stress_log_filesystem.py.exp | 84 ++++++++ 2 files changed, 272 insertions(+) create mode 100644 tests/multi_bluetooth/stress_log_filesystem.py create mode 100644 tests/multi_bluetooth/stress_log_filesystem.py.exp diff --git a/tests/multi_bluetooth/stress_log_filesystem.py b/tests/multi_bluetooth/stress_log_filesystem.py new file mode 100644 index 0000000000000..83df555ce4d8f --- /dev/null +++ b/tests/multi_bluetooth/stress_log_filesystem.py @@ -0,0 +1,188 @@ +# Test concurrency between filesystem access and BLE host. This is +# particularly relevant on STM32WB where the second core is stalled while +# flash operations are in progress. + +from micropython import const +import time, machine, bluetooth, os + +TIMEOUT_MS = 10000 + +LOG_PATH_INSTANCE0 = "stress_log_filesystem_0.log" +LOG_PATH_INSTANCE1 = "stress_log_filesystem_1.log" + +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_GATTS_WRITE = const(3) +_IRQ_GATTS_READ_REQUEST = const(4) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_GATTC_SERVICE_RESULT = const(9) +_IRQ_GATTC_SERVICE_DONE = const(10) +_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) +_IRQ_GATTC_CHARACTERISTIC_DONE = const(12) +_IRQ_GATTC_READ_RESULT = const(15) +_IRQ_GATTC_READ_DONE = const(16) +_IRQ_GATTC_WRITE_DONE = const(17) + +SERVICE_UUID = bluetooth.UUID("A5A5A5A5-FFFF-9999-1111-5A5A5A5A5A5A") +CHAR_UUID = bluetooth.UUID("00000000-1111-2222-3333-444444444444") +CHAR = ( + CHAR_UUID, + bluetooth.FLAG_READ | bluetooth.FLAG_WRITE | bluetooth.FLAG_NOTIFY | bluetooth.FLAG_INDICATE, +) +SERVICE = ( + SERVICE_UUID, + (CHAR,), +) +SERVICES = (SERVICE,) + + +waiting_events = {} +log_file = None + + +def write_log(*args): + if log_file: + print(*args, file=log_file) + log_file.flush() + + +last_file_write = 0 + + +def periodic_log_write(): + global last_file_write + t = time.ticks_ms() + if time.ticks_diff(t, last_file_write) > 50: + write_log("tick") + last_file_write = t + + +def irq(event, data): + write_log("event", event) + + if event == _IRQ_CENTRAL_CONNECT: + print("_IRQ_CENTRAL_CONNECT") + waiting_events[event] = data[0] + elif event == _IRQ_CENTRAL_DISCONNECT: + print("_IRQ_CENTRAL_DISCONNECT") + elif event == _IRQ_PERIPHERAL_CONNECT: + print("_IRQ_PERIPHERAL_CONNECT") + waiting_events[event] = data[0] + elif event == _IRQ_PERIPHERAL_DISCONNECT: + print("_IRQ_PERIPHERAL_DISCONNECT") + elif event == _IRQ_GATTC_SERVICE_RESULT: + # conn_handle, start_handle, end_handle, uuid = data + if data[-1] == SERVICE_UUID: + print("_IRQ_GATTC_SERVICE_RESULT", data[3]) + waiting_events[event] = (data[1], data[2]) + else: + return + elif event == _IRQ_GATTC_SERVICE_DONE: + print("_IRQ_GATTC_SERVICE_DONE") + elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT: + # conn_handle, def_handle, value_handle, properties, uuid = data + if data[-1] == CHAR_UUID: + print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) + waiting_events[event] = data[2] + else: + return + elif event == _IRQ_GATTC_CHARACTERISTIC_DONE: + print("_IRQ_GATTC_CHARACTERISTIC_DONE") + elif event == _IRQ_GATTC_READ_RESULT: + print("_IRQ_GATTC_READ_RESULT", bytes(data[-1])) + elif event == _IRQ_GATTC_READ_DONE: + print("_IRQ_GATTC_READ_DONE", data[-1]) + elif event == _IRQ_GATTC_WRITE_DONE: + print("_IRQ_GATTC_WRITE_DONE", data[-1]) + elif event == _IRQ_GATTS_WRITE: + print("_IRQ_GATTS_WRITE") + elif event == _IRQ_GATTS_READ_REQUEST: + print("_IRQ_GATTS_READ_REQUEST") + + if event not in waiting_events: + waiting_events[event] = None + + +def wait_for_event(event, timeout_ms): + t0 = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: + periodic_log_write() + if event in waiting_events: + return waiting_events.pop(event) + machine.idle() + raise ValueError("Timeout waiting for {}".format(event)) + + +# Acting in peripheral role. +def instance0(): + global log_file + log_file = open(LOG_PATH_INSTANCE0, "w") + write_log("start") + ble.active(1) + ble.irq(irq) + multitest.globals(BDADDR=ble.config("mac")) + ((char_handle,),) = ble.gatts_register_services(SERVICES) + multitest.next() + try: + for repeat in range(2): + print("gap_advertise") + ble.gap_advertise(50_000, b"\x02\x01\x06\x04\xffMPY") + # Wait for central to connect, do a sequence of read/write, then disconnect. + wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) + for op in range(4): + wait_for_event(_IRQ_GATTS_READ_REQUEST, TIMEOUT_MS) + wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS) + wait_for_event(_IRQ_CENTRAL_DISCONNECT, 2 * TIMEOUT_MS) + finally: + ble.active(0) + log_file.close() + os.unlink(LOG_PATH_INSTANCE0) + + +# Acting in central role. +def instance1(): + global log_file + log_file = open(LOG_PATH_INSTANCE1, "w") + write_log("start") + ble.active(1) + ble.irq(irq) + multitest.next() + try: + for repeat in range(2): + # Connect to peripheral and then disconnect. + print("gap_connect") + ble.gap_connect(BDADDR[0], BDADDR[1], 5000) + conn_handle = wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) + + # Discover services. + print("gattc_discover_services") + ble.gattc_discover_services(conn_handle) + start_handle, end_handle = wait_for_event(_IRQ_GATTC_SERVICE_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_SERVICE_DONE, TIMEOUT_MS) + + # Discover characteristics. + print("gattc_discover_characteristics") + ble.gattc_discover_characteristics(conn_handle, start_handle, end_handle) + value_handle = wait_for_event(_IRQ_GATTC_CHARACTERISTIC_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_CHARACTERISTIC_DONE, TIMEOUT_MS) + + for op in range(4): + print("gattc_read") + ble.gattc_read(conn_handle, value_handle) + wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_READ_DONE, TIMEOUT_MS) + print("gattc_write") + ble.gattc_write(conn_handle, value_handle, "{}".format(op), 1) + wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS) + + # Disconnect. + print("gap_disconnect:", ble.gap_disconnect(conn_handle)) + wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, 2 * TIMEOUT_MS) + finally: + ble.active(0) + log_file.close() + os.unlink(LOG_PATH_INSTANCE1) + + +ble = bluetooth.BLE() diff --git a/tests/multi_bluetooth/stress_log_filesystem.py.exp b/tests/multi_bluetooth/stress_log_filesystem.py.exp new file mode 100644 index 0000000000000..8e1144b78748e --- /dev/null +++ b/tests/multi_bluetooth/stress_log_filesystem.py.exp @@ -0,0 +1,84 @@ +--- instance0 --- +gap_advertise +_IRQ_CENTRAL_CONNECT +_IRQ_GATTS_READ_REQUEST +_IRQ_GATTS_WRITE +_IRQ_GATTS_READ_REQUEST +_IRQ_GATTS_WRITE +_IRQ_GATTS_READ_REQUEST +_IRQ_GATTS_WRITE +_IRQ_GATTS_READ_REQUEST +_IRQ_GATTS_WRITE +_IRQ_CENTRAL_DISCONNECT +gap_advertise +_IRQ_CENTRAL_CONNECT +_IRQ_GATTS_READ_REQUEST +_IRQ_GATTS_WRITE +_IRQ_GATTS_READ_REQUEST +_IRQ_GATTS_WRITE +_IRQ_GATTS_READ_REQUEST +_IRQ_GATTS_WRITE +_IRQ_GATTS_READ_REQUEST +_IRQ_GATTS_WRITE +_IRQ_CENTRAL_DISCONNECT +--- instance1 --- +gap_connect +_IRQ_PERIPHERAL_CONNECT +gattc_discover_services +_IRQ_GATTC_SERVICE_RESULT UUID('a5a5a5a5-ffff-9999-1111-5a5a5a5a5a5a') +_IRQ_GATTC_SERVICE_DONE +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_read +_IRQ_GATTC_READ_RESULT b'' +_IRQ_GATTC_READ_DONE 0 +gattc_write +_IRQ_GATTC_WRITE_DONE 0 +gattc_read +_IRQ_GATTC_READ_RESULT b'0' +_IRQ_GATTC_READ_DONE 0 +gattc_write +_IRQ_GATTC_WRITE_DONE 0 +gattc_read +_IRQ_GATTC_READ_RESULT b'1' +_IRQ_GATTC_READ_DONE 0 +gattc_write +_IRQ_GATTC_WRITE_DONE 0 +gattc_read +_IRQ_GATTC_READ_RESULT b'2' +_IRQ_GATTC_READ_DONE 0 +gattc_write +_IRQ_GATTC_WRITE_DONE 0 +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT +gap_connect +_IRQ_PERIPHERAL_CONNECT +gattc_discover_services +_IRQ_GATTC_SERVICE_RESULT UUID('a5a5a5a5-ffff-9999-1111-5a5a5a5a5a5a') +_IRQ_GATTC_SERVICE_DONE +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_read +_IRQ_GATTC_READ_RESULT b'3' +_IRQ_GATTC_READ_DONE 0 +gattc_write +_IRQ_GATTC_WRITE_DONE 0 +gattc_read +_IRQ_GATTC_READ_RESULT b'0' +_IRQ_GATTC_READ_DONE 0 +gattc_write +_IRQ_GATTC_WRITE_DONE 0 +gattc_read +_IRQ_GATTC_READ_RESULT b'1' +_IRQ_GATTC_READ_DONE 0 +gattc_write +_IRQ_GATTC_WRITE_DONE 0 +gattc_read +_IRQ_GATTC_READ_RESULT b'2' +_IRQ_GATTC_READ_DONE 0 +gattc_write +_IRQ_GATTC_WRITE_DONE 0 +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT From a64121b0d48ccd2d7212b6ff996d730eb85bfaad Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Wed, 4 Nov 2020 14:37:16 +1100 Subject: [PATCH 080/179] stm32/rfcore: Make RX IRQ schedule the NimBLE handler. This commit switches the STM32WB HCI interface (between the two CPUs) to require the use of MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS, and as a consequence to require NimBLE. IPCC RX IRQs now schedule the NimBLE handler to run via mp_sched_schedule. Signed-off-by: Jim Mussared --- ports/stm32/rfcore.c | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/ports/stm32/rfcore.c b/ports/stm32/rfcore.c index 08338fcdc25c5..550e5323e8526 100644 --- a/ports/stm32/rfcore.c +++ b/ports/stm32/rfcore.c @@ -31,6 +31,7 @@ #include "py/mperrno.h" #include "py/mphal.h" #include "py/runtime.h" +#include "extmod/modbluetooth.h" #include "rtc.h" #include "rfcore.h" @@ -40,6 +41,10 @@ #define DEBUG_printf(...) // printf("rfcore: " __VA_ARGS__) +#if !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS +#error "STM32WB must use synchronous events in the BLE implementation." +#endif + // Define to 1 to print traces of HCI packets #define HCI_TRACE (0) @@ -191,9 +196,6 @@ STATIC uint8_t ipcc_membuf_ble_cs_buf[272]; // mem2 STATIC tl_list_node_t ipcc_mem_ble_evt_queue; // mem1 STATIC uint8_t ipcc_membuf_ble_hci_acl_data_buf[272]; // mem2 -// Set by the RX IRQ handler on incoming HCI payload. -STATIC volatile bool had_ble_irq = false; - /******************************************************************************/ // Transport layer linked list @@ -408,6 +410,7 @@ STATIC void tl_process_msg(volatile tl_list_node_t *head, unsigned int ch, parse } } +// Only call this when IRQs are disabled on this channel. STATIC void tl_check_msg(volatile tl_list_node_t *head, unsigned int ch, parse_hci_info_t *parse) { if (LL_C2_IPCC_IsActiveFlag_CHx(IPCC, ch)) { tl_process_msg(head, ch, parse); @@ -417,14 +420,6 @@ STATIC void tl_check_msg(volatile tl_list_node_t *head, unsigned int ch, parse_h } } -STATIC void tl_check_msg_ble(volatile tl_list_node_t *head, parse_hci_info_t *parse) { - if (had_ble_irq) { - tl_process_msg(head, IPCC_CH_BLE, parse); - - had_ble_irq = false; - } -} - STATIC void tl_hci_cmd(uint8_t *cmd, unsigned int ch, uint8_t hdr, uint16_t opcode, const uint8_t *buf, size_t len) { tl_list_node_t *n = (tl_list_node_t *)cmd; n->next = n; @@ -472,7 +467,7 @@ STATIC ssize_t tl_sys_hci_cmd_resp(uint16_t opcode, const uint8_t *buf, size_t l STATIC int tl_ble_wait_resp(void) { uint32_t t0 = mp_hal_ticks_ms(); - while (!had_ble_irq) { + while (!LL_C2_IPCC_IsActiveFlag_CHx(IPCC, IPCC_CH_BLE)) { if (mp_hal_ticks_ms() - t0 > BLE_ACK_TIMEOUT_MS) { printf("tl_ble_wait_resp: timeout\n"); return -MP_ETIMEDOUT; @@ -480,14 +475,16 @@ STATIC int tl_ble_wait_resp(void) { } // C2 set IPCC flag. - tl_check_msg_ble(&ipcc_mem_ble_evt_queue, NULL); + tl_check_msg(&ipcc_mem_ble_evt_queue, IPCC_CH_BLE, NULL); return 0; } // Synchronously send a BLE command. STATIC void tl_ble_hci_cmd_resp(uint16_t opcode, const uint8_t *buf, size_t len) { + LL_C1_IPCC_DisableReceiveChannel(IPCC, IPCC_CH_BLE); tl_hci_cmd(ipcc_membuf_ble_cmd_buf, IPCC_CH_BLE, HCI_KIND_BT_CMD, opcode, buf, len); tl_ble_wait_resp(); + LL_C1_IPCC_EnableReceiveChannel(IPCC, IPCC_CH_BLE); } /******************************************************************************/ @@ -554,11 +551,10 @@ static const struct { void rfcore_ble_init(void) { DEBUG_printf("rfcore_ble_init\n"); - // Clear any outstanding messages from ipcc_init + // Clear any outstanding messages from ipcc_init. tl_check_msg(&ipcc_mem_sys_queue, IPCC_CH_SYS, NULL); - tl_check_msg_ble(&ipcc_mem_ble_evt_queue, NULL); - // Configure and reset the BLE controller + // Configure and reset the BLE controller. tl_sys_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_BLE_INIT), (const uint8_t *)&ble_init_params, sizeof(ble_init_params), 0); tl_ble_hci_cmd_resp(HCI_OPCODE(0x03, 0x0003), NULL, 0); } @@ -597,7 +593,7 @@ void rfcore_ble_hci_cmd(size_t len, const uint8_t *src) { void rfcore_ble_check_msg(int (*cb)(void *, const uint8_t *, size_t), void *env) { parse_hci_info_t parse = { cb, env, false }; - tl_check_msg_ble(&ipcc_mem_ble_evt_queue, &parse); + tl_process_msg(&ipcc_mem_ble_evt_queue, IPCC_CH_BLE, &parse); // Intercept HCI_Reset events and reconfigure the controller following the reset if (parse.was_hci_reset_evt) { @@ -629,9 +625,9 @@ void IPCC_C1_TX_IRQHandler(void) { void IPCC_C1_RX_IRQHandler(void) { IRQ_ENTER(IPCC_C1_RX_IRQn); - if (LL_C2_IPCC_IsActiveFlag_CHx(IPCC, IPCC_CH_BLE)) { - had_ble_irq = true; + DEBUG_printf("IPCC_C1_RX_IRQHandler\n"); + if (LL_C2_IPCC_IsActiveFlag_CHx(IPCC, IPCC_CH_BLE)) { LL_C1_IPCC_ClearFlag_CHx(IPCC, IPCC_CH_BLE); // Queue up the scheduler to process UART data and run events. From 119c88ef17c390e43828b825c3df99ffc1dc39b9 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Wed, 4 Nov 2020 15:29:31 +1100 Subject: [PATCH 081/179] stm32/flash: Implement WB55 flash locking. This is needed to moderate concurrent access to the internal flash, as while an erase/write is in progress execution will stall on the wireless core due to the bus being locked. This implements Figure 10 from AN5289 Rev 3. Signed-off-by: Jim Mussared --- ports/stm32/flash.c | 93 ++++++++++++++++++++++++++++++ ports/stm32/mboot/Makefile | 1 + ports/stm32/mpconfigboard_common.h | 4 ++ ports/stm32/rfcore.c | 24 ++++++-- ports/stm32/rfcore.h | 3 + 5 files changed, 121 insertions(+), 4 deletions(-) diff --git a/ports/stm32/flash.c b/ports/stm32/flash.c index 499129a6f3e5d..d399ece866e99 100644 --- a/ports/stm32/flash.c +++ b/ports/stm32/flash.c @@ -29,6 +29,21 @@ #include "py/mphal.h" #include "flash.h" +#if MICROPY_HW_STM32WB_FLASH_SYNCRONISATION +// See WB55 specific documentation in AN5289 Rev 3, and in particular, Figure 10. + +#include "rfcore.h" +#include "stm32wbxx_ll_hsem.h" + +// Protects all flash registers. +#define SEMID_FLASH_REGISTERS (2) +// Used by CPU1 to prevent CPU2 from writing/erasing data in flash memory. +#define SEMID_FLASH_CPU1 (6) +// Used by CPU2 to prevent CPU1 from writing/erasing data in flash memory. +#define SEMID_FLASH_CPU2 (7) + +#endif + typedef struct { uint32_t base_address; uint32_t sector_size; @@ -181,9 +196,27 @@ int flash_erase(uint32_t flash_dest, uint32_t num_word32) { return 0; } + #if MICROPY_HW_STM32WB_FLASH_SYNCRONISATION + // Acquire lock on the flash peripheral. + while (LL_HSEM_1StepLock(HSEM, SEMID_FLASH_REGISTERS)) { + } + #endif + // Unlock the flash for erase. HAL_FLASH_Unlock(); + #if MICROPY_HW_STM32WB_FLASH_SYNCRONISATION + // Tell the HCI controller stack we're starting an erase, so it + // avoids radio activity for a while. + rfcore_start_flash_erase(); + // Wait for PES. + while (LL_FLASH_IsActiveFlag_OperationSuspended()) { + } + // Wait for flash lock. + while (LL_HSEM_1StepLock(HSEM, SEMID_FLASH_CPU2)) { + } + #endif + // Clear pending flags (if any) and set up EraseInitStruct. FLASH_EraseInitTypeDef EraseInitStruct; @@ -233,9 +266,23 @@ int flash_erase(uint32_t flash_dest, uint32_t num_word32) { uint32_t SectorError = 0; HAL_StatusTypeDef status = HAL_FLASHEx_Erase(&EraseInitStruct, &SectorError); + #if MICROPY_HW_STM32WB_FLASH_SYNCRONISATION + // Release flash lock. + while (__HAL_FLASH_GET_FLAG(FLASH_FLAG_CFGBSY)) { + } + LL_HSEM_ReleaseLock(HSEM, SEMID_FLASH_CPU2, 0); + // Tell HCI controller that erase is over. + rfcore_end_flash_erase(); + #endif + // Lock the flash after erase. HAL_FLASH_Lock(); + #if MICROPY_HW_STM32WB_FLASH_SYNCRONISATION + // Release lock on the flash peripheral. + LL_HSEM_ReleaseLock(HSEM, SEMID_FLASH_REGISTERS, 0); + #endif + return mp_hal_status_to_neg_errno(status); } @@ -269,9 +316,21 @@ void flash_erase_it(uint32_t flash_dest, uint32_t num_word32) { */ int flash_write(uint32_t flash_dest, const uint32_t *src, uint32_t num_word32) { + #if MICROPY_HW_STM32WB_FLASH_SYNCRONISATION + // Acquire lock on the flash peripheral. + while (LL_HSEM_1StepLock(HSEM, SEMID_FLASH_REGISTERS)) { + } + #endif + // Unlock the flash for write. HAL_FLASH_Unlock(); + #if MICROPY_HW_STM32WB_FLASH_SYNCRONISATION + // Wait for PES. + while (LL_FLASH_IsActiveFlag_OperationSuspended()) { + } + #endif + HAL_StatusTypeDef status = HAL_OK; #if defined(STM32L4) || defined(STM32WB) @@ -279,7 +338,22 @@ int flash_write(uint32_t flash_dest, const uint32_t *src, uint32_t num_word32) { // program the flash uint64 by uint64 for (int i = 0; i < num_word32 / 2; i++) { uint64_t val = *(uint64_t *)src; + + #if MICROPY_HW_STM32WB_FLASH_SYNCRONISATION + // Wait for flash lock. + while (LL_HSEM_1StepLock(HSEM, SEMID_FLASH_CPU2)) { + } + #endif + status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, flash_dest, val); + + #if MICROPY_HW_STM32WB_FLASH_SYNCRONISATION + // Release flash lock. + LL_HSEM_ReleaseLock(HSEM, SEMID_FLASH_CPU2, 0); + while (__HAL_FLASH_GET_FLAG(FLASH_FLAG_CFGBSY)) { + } + #endif + if (status != HAL_OK) { num_word32 = 0; // don't write any odd word after this loop break; @@ -290,7 +364,21 @@ int flash_write(uint32_t flash_dest, const uint32_t *src, uint32_t num_word32) { if ((num_word32 & 0x01) == 1) { uint64_t val = *(uint64_t *)flash_dest; val = (val & 0xffffffff00000000uL) | (*src); + + #if MICROPY_HW_STM32WB_FLASH_SYNCRONISATION + // Wait for flash lock. + while (LL_HSEM_1StepLock(HSEM, SEMID_FLASH_CPU2)) { + } + #endif + status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, flash_dest, val); + + #if MICROPY_HW_STM32WB_FLASH_SYNCRONISATION + // Release flash lock. + LL_HSEM_ReleaseLock(HSEM, SEMID_FLASH_CPU2, 0); + while (__HAL_FLASH_GET_FLAG(FLASH_FLAG_CFGBSY)) { + } + #endif } #elif defined(STM32H7) @@ -322,6 +410,11 @@ int flash_write(uint32_t flash_dest, const uint32_t *src, uint32_t num_word32) { // Lock the flash after write. HAL_FLASH_Lock(); + #if MICROPY_HW_STM32WB_FLASH_SYNCRONISATION + // Release lock on the flash peripheral. + LL_HSEM_ReleaseLock(HSEM, SEMID_FLASH_REGISTERS, 0); + #endif + return mp_hal_status_to_neg_errno(status); } diff --git a/ports/stm32/mboot/Makefile b/ports/stm32/mboot/Makefile index c901dfb3344a4..3d041e1c141ea 100755 --- a/ports/stm32/mboot/Makefile +++ b/ports/stm32/mboot/Makefile @@ -73,6 +73,7 @@ CFLAGS += -DFFCONF_H=\"ports/stm32/mboot/ffconf.h\" CFLAGS += -DLFS1_NO_MALLOC -DLFS1_NO_DEBUG -DLFS1_NO_WARN -DLFS1_NO_ERROR -DLFS1_NO_ASSERT CFLAGS += -DLFS2_NO_MALLOC -DLFS2_NO_DEBUG -DLFS2_NO_WARN -DLFS2_NO_ERROR -DLFS2_NO_ASSERT CFLAGS += -DBUILDING_MBOOT=1 +CFLAGS += -DMICROPY_HW_STM32WB_FLASH_SYNCRONISATION=0 CFLAGS += -DBOOTLOADER_DFU_USB_VID=$(BOOTLOADER_DFU_USB_VID) -DBOOTLOADER_DFU_USB_PID=$(BOOTLOADER_DFU_USB_PID) LDFLAGS = -nostdlib -L . -T stm32_generic.ld -Map=$(@:.elf=.map) --cref diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index 127696b19374a..a73a26b1627b4 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -216,6 +216,10 @@ #define MICROPY_HW_MAX_TIMER (17) #define MICROPY_HW_MAX_UART (1) +#ifndef MICROPY_HW_STM32WB_FLASH_SYNCRONISATION +#define MICROPY_HW_STM32WB_FLASH_SYNCRONISATION (1) +#endif + #else #error Unsupported MCU series #endif diff --git a/ports/stm32/rfcore.c b/ports/stm32/rfcore.c index 550e5323e8526..6133720958093 100644 --- a/ports/stm32/rfcore.c +++ b/ports/stm32/rfcore.c @@ -57,10 +57,12 @@ #define OCF_CB_RESET (0x03) #define OCF_CB_SET_EVENT_MASK2 (0x63) -#define OGF_VENDOR (0x3f) -#define OCF_WRITE_CONFIG (0x0c) -#define OCF_SET_TX_POWER (0x0f) -#define OCF_BLE_INIT (0x66) +#define OGF_VENDOR (0x3f) +#define OCF_WRITE_CONFIG (0x0c) +#define OCF_SET_TX_POWER (0x0f) +#define OCF_BLE_INIT (0x66) +#define OCF_C2_FLASH_ERASE_ACTIVITY (0x69) +#define OCF_C2_SET_FLASH_ACTIVITY_CONTROL (0x73) #define HCI_OPCODE(ogf, ocf) ((ogf) << 10 | (ocf)) @@ -557,6 +559,10 @@ void rfcore_ble_init(void) { // Configure and reset the BLE controller. tl_sys_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_BLE_INIT), (const uint8_t *)&ble_init_params, sizeof(ble_init_params), 0); tl_ble_hci_cmd_resp(HCI_OPCODE(0x03, 0x0003), NULL, 0); + + // Enable PES rather than SEM7 to moderate flash access between the cores. + uint8_t buf = 0; // FLASH_ACTIVITY_CONTROL_PES + tl_sys_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_C2_SET_FLASH_ACTIVITY_CONTROL), &buf, 1, 0); } void rfcore_ble_hci_cmd(size_t len, const uint8_t *src) { @@ -616,6 +622,16 @@ void rfcore_ble_set_txpower(uint8_t level) { tl_ble_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_SET_TX_POWER), buf, 2); } +void rfcore_start_flash_erase(void) { + uint8_t buf = 1; // ERASE_ACTIVITY_ON + tl_sys_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_C2_FLASH_ERASE_ACTIVITY), &buf, 1, 0); +} + +void rfcore_end_flash_erase(void) { + uint8_t buf = 0; // ERASE_ACTIVITY_OFF + tl_sys_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_C2_FLASH_ERASE_ACTIVITY), &buf, 1, 0); +} + // IPCC IRQ Handlers void IPCC_C1_TX_IRQHandler(void) { IRQ_ENTER(IPCC_C1_TX_IRQn); diff --git a/ports/stm32/rfcore.h b/ports/stm32/rfcore.h index 6a3c85f67dfe8..fe29ac612ccc6 100644 --- a/ports/stm32/rfcore.h +++ b/ports/stm32/rfcore.h @@ -35,6 +35,9 @@ void rfcore_ble_hci_cmd(size_t len, const uint8_t *src); void rfcore_ble_check_msg(int (*cb)(void *, const uint8_t *, size_t), void *env); void rfcore_ble_set_txpower(uint8_t level); +void rfcore_start_flash_erase(void); +void rfcore_end_flash_erase(void); + MP_DECLARE_CONST_FUN_OBJ_0(rfcore_status_obj); MP_DECLARE_CONST_FUN_OBJ_1(rfcore_fw_version_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(rfcore_sys_hci_obj); From 21c293fbcd1f9d5bafac300b5da265b4efd2be52 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 10 Nov 2020 12:55:24 +1100 Subject: [PATCH 082/179] stm32/rfcore: Don't send HCI ACL cmds while another is pending. And, for TX, the next/prev entries ane unused so set them to NULL to indicate this. Signed-off-by: Jim Mussared --- ports/stm32/rfcore.c | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/ports/stm32/rfcore.c b/ports/stm32/rfcore.c index 6133720958093..da77be6117d38 100644 --- a/ports/stm32/rfcore.c +++ b/ports/stm32/rfcore.c @@ -32,6 +32,7 @@ #include "py/mphal.h" #include "py/runtime.h" #include "extmod/modbluetooth.h" +#include "nimble/nimble_npl.h" #include "rtc.h" #include "rfcore.h" @@ -72,8 +73,9 @@ #define HCI_KIND_VENDOR_RESPONSE (0x11) #define HCI_KIND_VENDOR_EVENT (0x12) -#define HCI_EVENT_COMMAND_COMPLETE (0x0E) // -#define HCI_EVENT_COMMAND_STATUS (0x0F) // +#define HCI_EVENT_COMMAND_COMPLETE (0x0E) // +#define HCI_EVENT_COMMAND_STATUS (0x0F) // +#define HCI_EVENT_NUMBER_OF_COMPLETED_PACKETS (0x13) // ()* #define SYS_ACK_TIMEOUT_MS (250) #define BLE_ACK_TIMEOUT_MS (250) @@ -83,6 +85,8 @@ // AN5289 #define MAGIC_IPCC_MEM_INCORRECT 0x3DE96F61 +volatile bool hci_acl_cmd_pending = false; + typedef struct _tl_list_node_t { volatile struct _tl_list_node_t *next; volatile struct _tl_list_node_t *prev; @@ -305,6 +309,11 @@ STATIC size_t tl_parse_hci_msg(const uint8_t *buf, parse_hci_info_t *parse) { case HCI_KIND_BT_EVENT: { info = "HCI_EVT"; + // Acknowledgment of a pending ACL request, allow another one to be sent. + if (buf[1] == HCI_EVENT_NUMBER_OF_COMPLETED_PACKETS) { + hci_acl_cmd_pending = false; + } + len = 3 + buf[2]; if (parse != NULL) { @@ -424,8 +433,8 @@ STATIC void tl_check_msg(volatile tl_list_node_t *head, unsigned int ch, parse_h STATIC void tl_hci_cmd(uint8_t *cmd, unsigned int ch, uint8_t hdr, uint16_t opcode, const uint8_t *buf, size_t len) { tl_list_node_t *n = (tl_list_node_t *)cmd; - n->next = n; - n->prev = n; + n->next = NULL; + n->prev = NULL; cmd[8] = hdr; cmd[9] = opcode; cmd[10] = opcode >> 8; @@ -584,13 +593,25 @@ void rfcore_ble_hci_cmd(size_t len, const uint8_t *src) { } else if (src[0] == HCI_KIND_BT_ACL) { n = (tl_list_node_t *)&ipcc_membuf_ble_hci_acl_data_buf[0]; ch = IPCC_CH_HCI_ACL; + + // Give the previous ACL command up to 100ms to complete. + mp_uint_t timeout_start_ticks_ms = mp_hal_ticks_ms(); + while (hci_acl_cmd_pending) { + if (mp_hal_ticks_ms() - timeout_start_ticks_ms > 100) { + break; + } + mp_bluetooth_nimble_hci_uart_wfi(); + } + + // Prevent sending another command until this one returns with HCI_EVENT_COMMAND_{COMPLETE,STATUS}. + hci_acl_cmd_pending = true; } else { printf("** UNEXPECTED HCI HDR: 0x%02x **\n", src[0]); return; } - n->next = n; - n->prev = n; + n->next = NULL; + n->prev = NULL; memcpy(n->body, src, len); // IPCC indicate. From 240b3de8bc0c0bf5ad04b92e4faae85c0f3a929c Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Wed, 18 Nov 2020 13:32:41 +1100 Subject: [PATCH 083/179] stm32/rfcore: Depend on NimBLE only when BLE enabled. This fixes the build for non-STM32WB based boards when the NimBLE submodule has not been fetched, and also allows STM32WB boards to build with BLE disabled. Signed-off-by: Jim Mussared --- ports/stm32/rfcore.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/ports/stm32/rfcore.c b/ports/stm32/rfcore.c index da77be6117d38..7d0a8520a6fc4 100644 --- a/ports/stm32/rfcore.c +++ b/ports/stm32/rfcore.c @@ -32,7 +32,6 @@ #include "py/mphal.h" #include "py/runtime.h" #include "extmod/modbluetooth.h" -#include "nimble/nimble_npl.h" #include "rtc.h" #include "rfcore.h" @@ -40,12 +39,23 @@ #include "stm32wbxx_ll_ipcc.h" -#define DEBUG_printf(...) // printf("rfcore: " __VA_ARGS__) +#if MICROPY_PY_BLUETOOTH + +#if MICROPY_BLUETOOTH_NIMBLE +// For mp_bluetooth_nimble_hci_uart_wfi +#include "nimble/nimble_npl.h" +#else +#error "STM32WB must use NimBLE." +#endif #if !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS -#error "STM32WB must use synchronous events in the BLE implementation." +#error "STM32WB must use synchronous BLE events." #endif +#endif + +#define DEBUG_printf(...) // printf("rfcore: " __VA_ARGS__) + // Define to 1 to print traces of HCI packets #define HCI_TRACE (0) @@ -600,7 +610,9 @@ void rfcore_ble_hci_cmd(size_t len, const uint8_t *src) { if (mp_hal_ticks_ms() - timeout_start_ticks_ms > 100) { break; } + #if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE mp_bluetooth_nimble_hci_uart_wfi(); + #endif } // Prevent sending another command until this one returns with HCI_EVENT_COMMAND_{COMPLETE,STATUS}. @@ -653,7 +665,9 @@ void rfcore_end_flash_erase(void) { tl_sys_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_C2_FLASH_ERASE_ACTIVITY), &buf, 1, 0); } +/******************************************************************************/ // IPCC IRQ Handlers + void IPCC_C1_TX_IRQHandler(void) { IRQ_ENTER(IPCC_C1_TX_IRQn); IRQ_EXIT(IPCC_C1_TX_IRQn); @@ -667,9 +681,11 @@ void IPCC_C1_RX_IRQHandler(void) { if (LL_C2_IPCC_IsActiveFlag_CHx(IPCC, IPCC_CH_BLE)) { LL_C1_IPCC_ClearFlag_CHx(IPCC, IPCC_CH_BLE); + #if MICROPY_PY_BLUETOOTH // Queue up the scheduler to process UART data and run events. extern void mp_bluetooth_hci_systick(uint32_t ticks_ms); mp_bluetooth_hci_systick(0); + #endif } IRQ_EXIT(IPCC_C1_RX_IRQn); From 5af3c046c7bfc1108094e53aea8b612c2ff86fd4 Mon Sep 17 00:00:00 2001 From: robert Date: Mon, 16 Nov 2020 20:51:37 +0100 Subject: [PATCH 084/179] esp32,esp8266: Remove "FAT" from warning message in inisetup.py. Because FAT is not any more the only filesystem used. --- ports/esp32/modules/inisetup.py | 2 +- ports/esp8266/modules/inisetup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/esp32/modules/inisetup.py b/ports/esp32/modules/inisetup.py index 9995d6bc07dd2..426a47a6b45e5 100644 --- a/ports/esp32/modules/inisetup.py +++ b/ports/esp32/modules/inisetup.py @@ -21,7 +21,7 @@ def fs_corrupted(): while 1: print( """\ -FAT filesystem appears to be corrupted. If you had important data there, you +The filesystem appears to be corrupted. If you had important data there, you may want to make a flash snapshot to try to recover it. Otherwise, perform factory reprogramming of MicroPython firmware (completely erase flash, followed by firmware programming). diff --git a/ports/esp8266/modules/inisetup.py b/ports/esp8266/modules/inisetup.py index 79576e9d62aa7..e711a57d4b609 100644 --- a/ports/esp8266/modules/inisetup.py +++ b/ports/esp8266/modules/inisetup.py @@ -30,7 +30,7 @@ def fs_corrupted(): while 1: print( """\ -The FAT filesystem starting at sector %d with size %d sectors appears to +The filesystem starting at sector %d with size %d sectors appears to be corrupted. If you had important data there, you may want to make a flash snapshot to try to recover it. Otherwise, perform factory reprogramming of MicroPython firmware (completely erase flash, followed by firmware From 3dcb551d8981bfb370bfe8f467fcde59dd7a916a Mon Sep 17 00:00:00 2001 From: JPFrancoia Date: Thu, 1 Oct 2020 09:51:58 +0200 Subject: [PATCH 085/179] nrf/README: Describe Pin numbering scheme for nRF52840. Clarify that the nRF52840's GPIO 1.00 to 1.15 maps to Pin(32-47) in MicroPython. --- ports/nrf/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ports/nrf/README.md b/ports/nrf/README.md index b08a034561e0c..5fceb6705f6d2 100644 --- a/ports/nrf/README.md +++ b/ports/nrf/README.md @@ -187,3 +187,12 @@ Other: * nRF UART application for IPhone/Android WebBluetooth mode can also be configured by editing `bluetooth_conf.h` and set `BLUETOOTH_WEBBLUETOOTH_REPL` to 1. This will alternate advertisement between Eddystone URL and regular connectable advertisement. The Eddystone URL will point the phone or PC to download [WebBluetooth REPL](https://aykevl.nl/apps/nus/) (experimental), which subsequently can be used to connect to the Bluetooth REPL from the PC or Phone browser. + + +## Pin numbering scheme for nrf52840-based boards + +Software Pins 0-31 correspond to physical pins 0.x and software Pins 32-47 +correspond to physical pins 1.x. + +Example: +`Pin(47)` would be 1.15 on the PCA10059 From 64180f0742a1926162b784c68bafa49b9b58596c Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 18 Nov 2020 14:50:43 +1100 Subject: [PATCH 086/179] extmod/machine_i2c: Add init protocol method for generic I2C bindings. Hardware I2C implementations must provide a .init() protocol method if they want to support reconfiguration. Otherwise the default is that i2c.init() raises an OSError (currently the case for all ports). mp_machine_soft_i2c_locals_dict is renamed to mp_machine_i2c_locals_dict to match the generic SPI bindings. Fixes issue #6623 (where calling .init() on a HW I2C would crash). Signed-off-by: Damien George --- extmod/machine_i2c.c | 87 +++++++++++++++++++-------------- extmod/machine_i2c.h | 9 ++-- ports/esp32/machine_i2c.c | 2 +- ports/nrf/modules/machine/i2c.c | 2 +- ports/stm32/machine_i2c.c | 2 +- ports/zephyr/machine_i2c.c | 2 +- 6 files changed, 59 insertions(+), 45 deletions(-) diff --git a/extmod/machine_i2c.c b/extmod/machine_i2c.c index 12c9abbcbaece..44161fbbb967a 100644 --- a/extmod/machine_i2c.c +++ b/extmod/machine_i2c.c @@ -300,45 +300,18 @@ STATIC int mp_machine_i2c_writeto(mp_obj_base_t *self, uint16_t addr, const uint } /******************************************************************************/ -// MicroPython bindings for I2C +// MicroPython bindings for generic machine.I2C -STATIC void mp_machine_soft_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - mp_machine_soft_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "SoftI2C(scl=" MP_HAL_PIN_FMT ", sda=" MP_HAL_PIN_FMT ", freq=%u)", - mp_hal_pin_name(self->scl), mp_hal_pin_name(self->sda), 500000 / self->us_delay); -} - -STATIC void machine_i2c_obj_init_helper(machine_i2c_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_scl, ARG_sda, ARG_freq, ARG_timeout }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_scl, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_sda, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 400000} }, - { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 255} }, - }; - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - self->scl = mp_hal_get_pin_obj(args[ARG_scl].u_obj); - self->sda = mp_hal_get_pin_obj(args[ARG_sda].u_obj); - self->us_timeout = args[ARG_timeout].u_int; - mp_hal_i2c_init(self, args[ARG_freq].u_int); -} - -STATIC mp_obj_t mp_machine_soft_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { - // create new soft I2C object - machine_i2c_obj_t *self = m_new_obj(machine_i2c_obj_t); - self->base.type = &mp_machine_soft_i2c_type; - mp_map_t kw_args; - mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); - machine_i2c_obj_init_helper(self, n_args, args, &kw_args); - return MP_OBJ_FROM_PTR(self); -} - -STATIC mp_obj_t machine_i2c_obj_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { - machine_i2c_obj_init_helper(MP_OBJ_TO_PTR(args[0]), n_args - 1, args + 1, kw_args); +STATIC mp_obj_t machine_i2c_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + mp_obj_base_t *self = (mp_obj_base_t *)MP_OBJ_TO_PTR(args[0]); + mp_machine_i2c_p_t *i2c_p = (mp_machine_i2c_p_t *)self->type->protocol; + if (i2c_p->init == NULL) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("I2C operation not supported")); + } + i2c_p->init(self, n_args - 1, args + 1, kw_args); return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2c_init_obj, 1, machine_i2c_obj_init); +MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2c_init_obj, 1, machine_i2c_init); STATIC mp_obj_t machine_i2c_scan(mp_obj_t self_in) { mp_obj_base_t *self = MP_OBJ_TO_PTR(self_in); @@ -653,8 +626,45 @@ STATIC const mp_rom_map_elem_t machine_i2c_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_readfrom_mem_into), MP_ROM_PTR(&machine_i2c_readfrom_mem_into_obj) }, { MP_ROM_QSTR(MP_QSTR_writeto_mem), MP_ROM_PTR(&machine_i2c_writeto_mem_obj) }, }; +MP_DEFINE_CONST_DICT(mp_machine_i2c_locals_dict, machine_i2c_locals_dict_table); -MP_DEFINE_CONST_DICT(mp_machine_soft_i2c_locals_dict, machine_i2c_locals_dict_table); +/******************************************************************************/ +// Implementation of soft I2C + +STATIC void mp_machine_soft_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + mp_machine_soft_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "SoftI2C(scl=" MP_HAL_PIN_FMT ", sda=" MP_HAL_PIN_FMT ", freq=%u)", + mp_hal_pin_name(self->scl), mp_hal_pin_name(self->sda), 500000 / self->us_delay); +} + +STATIC void mp_machine_soft_i2c_init(mp_obj_base_t *self_in, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_scl, ARG_sda, ARG_freq, ARG_timeout }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_scl, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_sda, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 400000} }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 255} }, + }; + + mp_machine_soft_i2c_obj_t *self = (mp_machine_soft_i2c_obj_t *)self_in; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + self->scl = mp_hal_get_pin_obj(args[ARG_scl].u_obj); + self->sda = mp_hal_get_pin_obj(args[ARG_sda].u_obj); + self->us_timeout = args[ARG_timeout].u_int; + mp_hal_i2c_init(self, args[ARG_freq].u_int); +} + +STATIC mp_obj_t mp_machine_soft_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // create new soft I2C object + machine_i2c_obj_t *self = m_new_obj(machine_i2c_obj_t); + self->base.type = &mp_machine_soft_i2c_type; + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + mp_machine_soft_i2c_init(&self->base, n_args, args, &kw_args); + return MP_OBJ_FROM_PTR(self); +} int mp_machine_soft_i2c_read(mp_obj_base_t *self_in, uint8_t *dest, size_t len, bool nack) { machine_i2c_obj_t *self = (machine_i2c_obj_t *)self_in; @@ -684,6 +694,7 @@ int mp_machine_soft_i2c_write(mp_obj_base_t *self_in, const uint8_t *src, size_t } STATIC const mp_machine_i2c_p_t mp_machine_soft_i2c_p = { + .init = mp_machine_soft_i2c_init, .start = (int (*)(mp_obj_base_t *))mp_hal_i2c_start, .stop = (int (*)(mp_obj_base_t *))mp_hal_i2c_stop, .read = mp_machine_soft_i2c_read, @@ -697,7 +708,7 @@ const mp_obj_type_t mp_machine_soft_i2c_type = { .print = mp_machine_soft_i2c_print, .make_new = mp_machine_soft_i2c_make_new, .protocol = &mp_machine_soft_i2c_p, - .locals_dict = (mp_obj_dict_t *)&mp_machine_soft_i2c_locals_dict, + .locals_dict = (mp_obj_dict_t *)&mp_machine_i2c_locals_dict, }; #endif // MICROPY_PY_MACHINE_I2C diff --git a/extmod/machine_i2c.h b/extmod/machine_i2c.h index e3a87e282a0fe..9e43747323840 100644 --- a/extmod/machine_i2c.h +++ b/extmod/machine_i2c.h @@ -51,9 +51,12 @@ typedef struct _mp_machine_i2c_buf_t { } mp_machine_i2c_buf_t; // I2C protocol -// the first 4 methods can be NULL, meaning operation is not supported -// transfer_single only needs to be set if transfer=mp_machine_i2c_transfer_adaptor +// - init must be non-NULL +// - start/stop/read/write can be NULL, meaning operation is not supported +// - transfer must be non-NULL +// - transfer_single only needs to be set if transfer=mp_machine_i2c_transfer_adaptor typedef struct _mp_machine_i2c_p_t { + void (*init)(mp_obj_base_t *obj, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); int (*start)(mp_obj_base_t *obj); int (*stop)(mp_obj_base_t *obj); int (*read)(mp_obj_base_t *obj, uint8_t *dest, size_t len, bool nack); @@ -71,7 +74,7 @@ typedef struct _mp_machine_soft_i2c_obj_t { } mp_machine_soft_i2c_obj_t; extern const mp_obj_type_t mp_machine_soft_i2c_type; -extern const mp_obj_dict_t mp_machine_soft_i2c_locals_dict; +extern const mp_obj_dict_t mp_machine_i2c_locals_dict; int mp_machine_i2c_transfer_adaptor(mp_obj_base_t *self, uint16_t addr, size_t n, mp_machine_i2c_buf_t *bufs, unsigned int flags); int mp_machine_soft_i2c_transfer(mp_obj_base_t *self, uint16_t addr, size_t n, mp_machine_i2c_buf_t *bufs, unsigned int flags); diff --git a/ports/esp32/machine_i2c.c b/ports/esp32/machine_i2c.c index a1d0ad0f4d93c..87b08fc9bc6e6 100644 --- a/ports/esp32/machine_i2c.c +++ b/ports/esp32/machine_i2c.c @@ -177,5 +177,5 @@ const mp_obj_type_t machine_hw_i2c_type = { .print = machine_hw_i2c_print, .make_new = machine_hw_i2c_make_new, .protocol = &machine_hw_i2c_p, - .locals_dict = (mp_obj_dict_t *)&mp_machine_soft_i2c_locals_dict, + .locals_dict = (mp_obj_dict_t *)&mp_machine_i2c_locals_dict, }; diff --git a/ports/nrf/modules/machine/i2c.c b/ports/nrf/modules/machine/i2c.c index ab4d516621bc7..aac9320873207 100644 --- a/ports/nrf/modules/machine/i2c.c +++ b/ports/nrf/modules/machine/i2c.c @@ -167,7 +167,7 @@ const mp_obj_type_t machine_hard_i2c_type = { .print = machine_hard_i2c_print, .make_new = machine_hard_i2c_make_new, .protocol = &machine_hard_i2c_p, - .locals_dict = (mp_obj_dict_t*)&mp_machine_soft_i2c_locals_dict, + .locals_dict = (mp_obj_dict_t*)&mp_machine_i2c_locals_dict, }; #endif // MICROPY_PY_MACHINE_I2C diff --git a/ports/stm32/machine_i2c.c b/ports/stm32/machine_i2c.c index e0c408c6d07c2..cfa00b86d830b 100644 --- a/ports/stm32/machine_i2c.c +++ b/ports/stm32/machine_i2c.c @@ -273,7 +273,7 @@ const mp_obj_type_t machine_hard_i2c_type = { .print = machine_hard_i2c_print, .make_new = machine_hard_i2c_make_new, .protocol = &machine_hard_i2c_p, - .locals_dict = (mp_obj_dict_t *)&mp_machine_soft_i2c_locals_dict, + .locals_dict = (mp_obj_dict_t *)&mp_machine_i2c_locals_dict, }; #endif // MICROPY_HW_ENABLE_HW_I2C diff --git a/ports/zephyr/machine_i2c.c b/ports/zephyr/machine_i2c.c index ec4b8620a5014..efd4bdcb21b58 100644 --- a/ports/zephyr/machine_i2c.c +++ b/ports/zephyr/machine_i2c.c @@ -134,5 +134,5 @@ const mp_obj_type_t machine_hard_i2c_type = { .print = machine_hard_i2c_print, .make_new = machine_hard_i2c_make_new, .protocol = &machine_hard_i2c_p, - .locals_dict = (mp_obj_dict_t *)&mp_machine_soft_i2c_locals_dict, + .locals_dict = (mp_obj_dict_t *)&mp_machine_i2c_locals_dict, }; From 0e8af2b3708ab3e499491836d481b10e030408f8 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 16 Oct 2020 00:15:16 +1100 Subject: [PATCH 087/179] extmod/modbluetooth: Add API for L2CAP channels. Also known as L2CAP "connection oriented channels". This provides a socket-like data transfer mechanism for BLE. Currently only implemented for NimBLE on STM32 / Unix. Signed-off-by: Jim Mussared --- extmod/modbluetooth.c | 129 +++++++++-- extmod/modbluetooth.h | 35 ++- extmod/nimble/modbluetooth_nimble.c | 332 +++++++++++++++++++++++++++ extmod/nimble/modbluetooth_nimble.h | 6 + extmod/nimble/nimble/nimble_npl_os.h | 13 ++ extmod/nimble/syscfg/syscfg.h | 6 +- ports/stm32/Makefile | 1 + ports/unix/Makefile | 1 + py/misc.h | 3 + 9 files changed, 504 insertions(+), 22 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index d8068df594538..2cff386f125d5 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -43,6 +43,10 @@ #error modbluetooth requires MICROPY_ENABLE_SCHEDULER #endif +#if MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS && !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS +#error l2cap channels require synchronous modbluetooth events +#endif + #define MP_BLUETOOTH_CONNECT_DEFAULT_SCAN_DURATION_MS 2000 #define MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN 5 @@ -785,6 +789,58 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gattc_exchange_mtu_obj, bluetooth #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +#if MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS + +STATIC mp_obj_t bluetooth_ble_l2cap_listen(mp_obj_t self_in, mp_obj_t psm_in, mp_obj_t mtu_in) { + (void)self_in; + mp_int_t psm = mp_obj_get_int(psm_in); + mp_int_t mtu = mp_obj_get_int(mtu_in); + return bluetooth_handle_errno(mp_bluetooth_l2cap_listen(psm, mtu)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(bluetooth_ble_l2cap_listen_obj, bluetooth_ble_l2cap_listen); + +STATIC mp_obj_t bluetooth_ble_l2cap_connect(size_t n_args, const mp_obj_t *args) { + mp_int_t conn_handle = mp_obj_get_int(args[1]); + mp_int_t psm = mp_obj_get_int(args[2]); + mp_int_t mtu = mp_obj_get_int(args[3]); + return bluetooth_handle_errno(mp_bluetooth_l2cap_connect(conn_handle, psm, mtu)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_l2cap_connect_obj, 4, 4, bluetooth_ble_l2cap_connect); + +STATIC mp_obj_t bluetooth_ble_l2cap_disconnect(mp_obj_t self_in, mp_obj_t conn_handle_in, mp_obj_t cid_in) { + (void)self_in; + mp_int_t conn_handle = mp_obj_get_int(conn_handle_in); + mp_int_t cid = mp_obj_get_int(cid_in); + return bluetooth_handle_errno(mp_bluetooth_l2cap_disconnect(conn_handle, cid)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(bluetooth_ble_l2cap_disconnect_obj, bluetooth_ble_l2cap_disconnect); + +STATIC mp_obj_t bluetooth_ble_l2cap_send(size_t n_args, const mp_obj_t *args) { + mp_int_t conn_handle = mp_obj_get_int(args[1]); + mp_int_t cid = mp_obj_get_int(args[2]); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[3], &bufinfo, MP_BUFFER_READ); + bool stalled = false; + bluetooth_handle_errno(mp_bluetooth_l2cap_send(conn_handle, cid, bufinfo.buf, bufinfo.len, &stalled)); + // Return True if the channel is still ready to send. + return mp_obj_new_bool(!stalled); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_l2cap_send_obj, 4, 4, bluetooth_ble_l2cap_send); + +STATIC mp_obj_t bluetooth_ble_l2cap_recvinto(size_t n_args, const mp_obj_t *args) { + mp_int_t conn_handle = mp_obj_get_int(args[1]); + mp_int_t cid = mp_obj_get_int(args[2]); + mp_buffer_info_t bufinfo = {0}; + if (args[3] != mp_const_none) { + mp_get_buffer_raise(args[3], &bufinfo, MP_BUFFER_WRITE); + } + bluetooth_handle_errno(mp_bluetooth_l2cap_recvinto(conn_handle, cid, bufinfo.buf, &bufinfo.len)); + return MP_OBJ_NEW_SMALL_INT(bufinfo.len); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_l2cap_recvinto_obj, 4, 4, bluetooth_ble_l2cap_recvinto); + +#endif // MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS + // ---------------------------------------------------------------------------- // Bluetooth object: Definition // ---------------------------------------------------------------------------- @@ -817,6 +873,13 @@ STATIC const mp_rom_map_elem_t bluetooth_ble_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_gattc_write), MP_ROM_PTR(&bluetooth_ble_gattc_write_obj) }, { MP_ROM_QSTR(MP_QSTR_gattc_exchange_mtu), MP_ROM_PTR(&bluetooth_ble_gattc_exchange_mtu_obj) }, #endif + #if MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS + { MP_ROM_QSTR(MP_QSTR_l2cap_listen), MP_ROM_PTR(&bluetooth_ble_l2cap_listen_obj) }, + { MP_ROM_QSTR(MP_QSTR_l2cap_connect), MP_ROM_PTR(&bluetooth_ble_l2cap_connect_obj) }, + { MP_ROM_QSTR(MP_QSTR_l2cap_disconnect), MP_ROM_PTR(&bluetooth_ble_l2cap_disconnect_obj) }, + { MP_ROM_QSTR(MP_QSTR_l2cap_send), MP_ROM_PTR(&bluetooth_ble_l2cap_send_obj) }, + { MP_ROM_QSTR(MP_QSTR_l2cap_recvinto), MP_ROM_PTR(&bluetooth_ble_l2cap_recvinto_obj) }, + #endif }; STATIC MP_DEFINE_CONST_DICT(bluetooth_ble_locals_dict, bluetooth_ble_locals_dict_table); @@ -1051,6 +1114,37 @@ void mp_bluetooth_gatts_on_mtu_exchanged(uint16_t conn_handle, uint16_t value) { invoke_irq_handler(MP_BLUETOOTH_IRQ_MTU_EXCHANGED, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); } +#if MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS +mp_int_t mp_bluetooth_gattc_on_l2cap_accept(uint16_t conn_handle, uint16_t cid, uint16_t psm, uint16_t our_mtu, uint16_t peer_mtu) { + uint16_t args[] = {conn_handle, cid, psm, our_mtu, peer_mtu}; + mp_obj_t result = invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_ACCEPT, args, 5, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); + // Return non-zero from IRQ handler to fail the accept. + mp_int_t ret = 0; + mp_obj_get_int_maybe(result, &ret); + return ret; +} + +void mp_bluetooth_gattc_on_l2cap_connect(uint16_t conn_handle, uint16_t cid, uint16_t psm, uint16_t our_mtu, uint16_t peer_mtu) { + uint16_t args[] = {conn_handle, cid, psm, our_mtu, peer_mtu}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_CONNECT, args, 5, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); +} + +void mp_bluetooth_gattc_on_l2cap_disconnect(uint16_t conn_handle, uint16_t cid, uint16_t psm, uint16_t status) { + uint16_t args[] = {conn_handle, cid, psm, status}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_DISCONNECT, args, 4, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); +} + +void mp_bluetooth_gattc_on_l2cap_send_ready(uint16_t conn_handle, uint16_t cid, uint8_t status) { + uint16_t args[] = {conn_handle, cid}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_SEND_READY, args, 2, &status, 1, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); +} + +void mp_bluetooth_gattc_on_l2cap_recv(uint16_t conn_handle, uint16_t cid) { + uint16_t args[] = {conn_handle, cid}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_RECV, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); +} +#endif // MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE void mp_bluetooth_gap_on_scan_complete(void) { invoke_irq_handler(MP_BLUETOOTH_IRQ_SCAN_DONE, NULL_U16, 0, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); @@ -1203,6 +1297,23 @@ void mp_bluetooth_gatts_on_indicate_complete(uint16_t conn_handle, uint16_t valu schedule_ringbuf(atomic_state); } +bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle) { + (void)conn_handle; + (void)value_handle; + // This must be handled synchronously and therefore cannot implemented with the ringbuffer. + return true; +} + +void mp_bluetooth_gatts_on_mtu_exchanged(uint16_t conn_handle, uint16_t value) { + MICROPY_PY_BLUETOOTH_ENTER + mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); + if (enqueue_irq(o, 2 + 2, MP_BLUETOOTH_IRQ_MTU_EXCHANGED)) { + ringbuf_put16(&o->ringbuf, conn_handle); + ringbuf_put16(&o->ringbuf, value); + } + schedule_ringbuf(atomic_state); +} + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE void mp_bluetooth_gap_on_scan_complete(void) { MICROPY_PY_BLUETOOTH_ENTER @@ -1322,26 +1433,8 @@ void mp_bluetooth_gattc_on_read_write_status(uint8_t event, uint16_t conn_handle } schedule_ringbuf(atomic_state); } - #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE -bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle) { - (void)conn_handle; - (void)value_handle; - // This must be handled synchronously and therefore cannot implemented with the ringbuffer. - return true; -} - -void mp_bluetooth_gatts_on_mtu_exchanged(uint16_t conn_handle, uint16_t value) { - MICROPY_PY_BLUETOOTH_ENTER - mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - if (enqueue_irq(o, 2 + 2, MP_BLUETOOTH_IRQ_MTU_EXCHANGED)) { - ringbuf_put16(&o->ringbuf, conn_handle); - ringbuf_put16(&o->ringbuf, value); - } - schedule_ringbuf(atomic_state); -} - #endif // MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS // ---------------------------------------------------------------------------- diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 52e3446ff3b94..9caddb0f3f89e 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -49,6 +49,11 @@ #define MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS (0) #endif +// A port can optionally enable support for L2CAP "Connection Oriented Channels". +#ifndef MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS +#define MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS (0) +#endif + // This is used to protect the ringbuffer. // A port may no-op this if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS is enabled. #ifndef MICROPY_PY_BLUETOOTH_ENTER @@ -104,6 +109,11 @@ #define MP_BLUETOOTH_IRQ_GATTC_INDICATE (19) #define MP_BLUETOOTH_IRQ_GATTS_INDICATE_DONE (20) #define MP_BLUETOOTH_IRQ_MTU_EXCHANGED (21) +#define MP_BLUETOOTH_IRQ_L2CAP_ACCEPT (22) +#define MP_BLUETOOTH_IRQ_L2CAP_CONNECT (23) +#define MP_BLUETOOTH_IRQ_L2CAP_DISCONNECT (24) +#define MP_BLUETOOTH_IRQ_L2CAP_RECV (25) +#define MP_BLUETOOTH_IRQ_L2CAP_SEND_READY (26) #define MP_BLUETOOTH_ADDRESS_MODE_PUBLIC (0) #define MP_BLUETOOTH_ADDRESS_MODE_RANDOM (1) @@ -136,6 +146,11 @@ _IRQ_GATTC_NOTIFY = const(18) _IRQ_GATTC_INDICATE = const(19) _IRQ_GATTS_INDICATE_DONE = const(20) _IRQ_MTU_EXCHANGED = const(21) +_IRQ_L2CAP_ACCEPT = const(22) +_IRQ_L2CAP_CONNECT = const(23) +_IRQ_L2CAP_DISCONNECT = const(24) +_IRQ_L2CAP_RECV = const(25) +_IRQ_L2CAP_SEND_READY = const(26) */ // bluetooth.UUID type. @@ -250,7 +265,15 @@ int mp_bluetooth_gattc_write(uint16_t conn_handle, uint16_t value_handle, const // Initiate MTU exchange for a specific connection using the preferred MTU. int mp_bluetooth_gattc_exchange_mtu(uint16_t conn_handle); -#endif +#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + +#if MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS +int mp_bluetooth_l2cap_listen(uint16_t psm, uint16_t mtu); +int mp_bluetooth_l2cap_connect(uint16_t conn_handle, uint16_t psm, uint16_t mtu); +int mp_bluetooth_l2cap_disconnect(uint16_t conn_handle, uint16_t cid); +int mp_bluetooth_l2cap_send(uint16_t conn_handle, uint16_t cid, const uint8_t *buf, size_t len, bool *stalled); +int mp_bluetooth_l2cap_recvinto(uint16_t conn_handle, uint16_t cid, uint8_t *buf, size_t *len); +#endif // MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS ///////////////////////////////////////////////////////////////////////////// // API implemented by modbluetooth (called by port-specific implementations): @@ -294,7 +317,15 @@ void mp_bluetooth_gattc_on_data_available(uint8_t event, uint16_t conn_handle, u // Notify modbluetooth that a read or write operation has completed. void mp_bluetooth_gattc_on_read_write_status(uint8_t event, uint16_t conn_handle, uint16_t value_handle, uint16_t status); -#endif +#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + +#if MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS +mp_int_t mp_bluetooth_gattc_on_l2cap_accept(uint16_t conn_handle, uint16_t cid, uint16_t psm, uint16_t our_mtu, uint16_t peer_mtu); +void mp_bluetooth_gattc_on_l2cap_connect(uint16_t conn_handle, uint16_t cid, uint16_t psm, uint16_t our_mtu, uint16_t peer_mtu); +void mp_bluetooth_gattc_on_l2cap_disconnect(uint16_t conn_handle, uint16_t cid, uint16_t psm, uint16_t status); +void mp_bluetooth_gattc_on_l2cap_send_ready(uint16_t conn_handle, uint16_t cid, uint8_t status); +void mp_bluetooth_gattc_on_l2cap_recv(uint16_t conn_handle, uint16_t cid); +#endif // MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS // For stacks that don't manage attribute value data (currently all of them), helpers // to store this in a map, keyed by value handle. diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index c961aee3262c9..fa416ebc3f529 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -42,6 +42,10 @@ #include "services/gap/ble_svc_gap.h" #include "services/gatt/ble_svc_gatt.h" +// We need the definition of "struct ble_l2cap_chan". +// See l2cap_channel_event() for details. +#include "nimble/host/src/ble_l2cap_priv.h" + #ifndef MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME #define MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME "MPY NIMBLE" #endif @@ -66,6 +70,7 @@ STATIC int8_t ble_hs_err_to_errno_table[] = { [BLE_HS_ETIMEOUT] = MP_ETIMEDOUT, [BLE_HS_EDONE] = MP_EIO, // TODO: Maybe should be MP_EISCONN (connect uses this for "already connected"). [BLE_HS_EBUSY] = MP_EBUSY, + [BLE_HS_EBADDATA] = MP_EINVAL, }; STATIC int ble_hs_err_to_errno(int err) { @@ -1110,4 +1115,331 @@ int mp_bluetooth_gattc_exchange_mtu(uint16_t conn_handle) { #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE +#if MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS + +// Fortunately NimBLE uses mbuf chains correctly with L2CAP COC (rather than +// accessing the mbuf internals directly), so we can use a small block size to +// avoid excessive fragmentation and rely on them chaining together for larger +// payloads. +#define L2CAP_BUF_BLOCK_SIZE (128) + +// This gives us enough room to have one MTU-size transmit buffer and two +// MTU-sized receive buffers. Note that we use the local MTU to calculate +// the buffer size. This means that if the peer MTU is larger, then +// there might not be enough space in the pool to send a full peer-MTU +// sized payload and mp_bluetooth_l2cap_send will return ENOMEM. +#define L2CAP_BUF_SIZE_MTUS_PER_CHANNEL (3) + +typedef struct _mp_bluetooth_nimble_l2cap_channel_t { + struct ble_l2cap_chan *chan; + struct os_mbuf_pool sdu_mbuf_pool; + struct os_mempool sdu_mempool; + struct os_mbuf *rx_pending; + uint16_t mtu; + os_membuf_t sdu_mem[]; +} mp_bluetooth_nimble_l2cap_channel_t; + +STATIC void destroy_l2cap_channel() { + // Only free the l2cap channel if we're the one that initiated the connection. + // Listeners continue listening on the same channel. + if (!MP_STATE_PORT(bluetooth_nimble_root_pointers)->l2cap_listening) { + MP_STATE_PORT(bluetooth_nimble_root_pointers)->l2cap_chan = NULL; + } +} + +STATIC int l2cap_channel_event(struct ble_l2cap_event *event, void *arg) { + DEBUG_printf("l2cap_channel_event: type=%d\n", event->type); + mp_bluetooth_nimble_l2cap_channel_t *chan = (mp_bluetooth_nimble_l2cap_channel_t *)arg; + struct ble_l2cap_chan_info info; + + switch (event->type) { + case BLE_L2CAP_EVENT_COC_CONNECTED: { + DEBUG_printf("l2cap_channel_event: connect: conn_handle=%d status=%d\n", event->connect.conn_handle, event->connect.status); + chan->chan = event->connect.chan; + + ble_l2cap_get_chan_info(event->connect.chan, &info); + if (event->connect.status == 0) { + mp_bluetooth_gattc_on_l2cap_connect(event->connect.conn_handle, info.scid, info.psm, info.our_coc_mtu, info.peer_coc_mtu); + } else { + mp_bluetooth_gattc_on_l2cap_disconnect(event->connect.conn_handle, info.scid, info.psm, event->connect.status); + destroy_l2cap_channel(); + } + break; + } + case BLE_L2CAP_EVENT_COC_DISCONNECTED: { + DEBUG_printf("l2cap_channel_event: disconnect: conn_handle=%d\n", event->disconnect.conn_handle); + ble_l2cap_get_chan_info(event->disconnect.chan, &info); + mp_bluetooth_gattc_on_l2cap_disconnect(event->disconnect.conn_handle, info.scid, info.psm, 0); + destroy_l2cap_channel(); + break; + } + case BLE_L2CAP_EVENT_COC_ACCEPT: { + DEBUG_printf("l2cap_channel_event: accept: conn_handle=%d peer_sdu_size=%d\n", event->accept.conn_handle, event->accept.peer_sdu_size); + chan->chan = event->accept.chan; + ble_l2cap_get_chan_info(event->accept.chan, &info); + int ret = mp_bluetooth_gattc_on_l2cap_accept(event->accept.conn_handle, info.scid, info.psm, info.our_coc_mtu, info.peer_coc_mtu); + if (ret != 0) { + return ret; + } + struct os_mbuf *sdu_rx = os_mbuf_get_pkthdr(&chan->sdu_mbuf_pool, 0); + assert(sdu_rx); + return ble_l2cap_recv_ready(chan->chan, sdu_rx); + } + case BLE_L2CAP_EVENT_COC_DATA_RECEIVED: { + DEBUG_printf("l2cap_channel_event: receive: conn_handle=%d len=%d\n", event->receive.conn_handle, OS_MBUF_PKTLEN(event->receive.sdu_rx)); + + if (chan->rx_pending) { + // Ideally this doesn't happen, as the sender should not get + // any more credits to send more data until we call + // ble_l2cap_recv_ready. However there might be multiple + // in-flight packets if the sender was able to send more than + // one before stalling. + DEBUG_printf("l2cap_channel_event: receive: appending to rx pending\n"); + // Note: os_mbuf_concat will just join the two together, so + // sdu_rx is now "owned" by rx_pending. + os_mbuf_concat(chan->rx_pending, event->receive.sdu_rx); + } else { + // Normal case is when the first payload arrives since calling + // ble_l2cap_recv_ready. + DEBUG_printf("l2cap_event: receive: new payload\n"); + // Take ownership of sdu_rx. + chan->rx_pending = event->receive.sdu_rx; + } + + struct os_mbuf *sdu_rx = os_mbuf_get_pkthdr(&chan->sdu_mbuf_pool, 0); + assert(sdu_rx); + + // ble_l2cap_coc_rx_fn invokes this event handler when a complete payload arrives. + // However, it NULLs chan->chan->coc_rx.sdu before doing so, expecting that + // ble_l2cap_recv_ready will be called to give it a new mbuf. + // This means that if another payload arrives before we call ble_l2cap_recv_ready + // then ble_l2cap_coc_rx_fn will NULL-deref coc_rx.sdu. + + // Because we're not yet ready to grant new credits to the channel, we can't call + // ble_l2cap_recv_ready yet, so instead we just give it a new mbuf. This requires + // ble_l2cap_priv.h for the definition of chan->chan (i.e. struct ble_l2cap_chan). + chan->chan->coc_rx.sdu = sdu_rx; + + ble_l2cap_get_chan_info(event->receive.chan, &info); + mp_bluetooth_gattc_on_l2cap_recv(event->receive.conn_handle, info.scid); + break; + } + case BLE_L2CAP_EVENT_COC_TX_UNSTALLED: { + DEBUG_printf("l2cap_channel_event: tx_unstalled: conn_handle=%d status=%d\n", event->tx_unstalled.conn_handle, event->tx_unstalled.status); + ble_l2cap_get_chan_info(event->receive.chan, &info); + // Map status to {0,1} (i.e. "sent everything", or "partial send"). + mp_bluetooth_gattc_on_l2cap_send_ready(event->tx_unstalled.conn_handle, info.scid, event->tx_unstalled.status == 0 ? 0 : 1); + break; + } + case BLE_L2CAP_EVENT_COC_RECONFIG_COMPLETED: { + DEBUG_printf("l2cap_channel_event: reconfig_completed: conn_handle=%d\n", event->reconfigured.conn_handle); + break; + } + case BLE_L2CAP_EVENT_COC_PEER_RECONFIGURED: { + DEBUG_printf("l2cap_channel_event: peer_reconfigured: conn_handle=%d\n", event->reconfigured.conn_handle); + break; + } + default: { + DEBUG_printf("l2cap_channel_event: unknown event\n"); + break; + } + } + + return 0; +} + +STATIC mp_bluetooth_nimble_l2cap_channel_t *get_l2cap_channel_for_conn_cid(uint16_t conn_handle, uint16_t cid) { + // TODO: Support more than one concurrent L2CAP channel. At the moment we + // just verify that the cid refers to the current channel. + mp_bluetooth_nimble_l2cap_channel_t *chan = MP_STATE_PORT(bluetooth_nimble_root_pointers)->l2cap_chan; + + if (!chan) { + return NULL; + } + + struct ble_l2cap_chan_info info; + ble_l2cap_get_chan_info(chan->chan, &info); + + if (info.scid != cid || ble_l2cap_get_conn_handle(chan->chan) != conn_handle) { + return NULL; + } + + return chan; +} + +STATIC int create_l2cap_channel(uint16_t mtu, mp_bluetooth_nimble_l2cap_channel_t **out) { + if (MP_STATE_PORT(bluetooth_nimble_root_pointers)->l2cap_chan) { + // Only one L2CAP channel allowed. + // Additionally, if we're listening, then no connections may be initiated. + DEBUG_printf("create_l2cap_channel: channel already in use\n"); + return MP_EALREADY; + } + + // We want the TX and RX buffers to share a pool that is some multiple of + // the MTU size. Figure out how many blocks per MTU (rounding up), then + // multiply that by the "MTUs per channel" (set to 3 above). + const size_t buf_blocks = MP_CEIL_DIVIDE(mtu, L2CAP_BUF_BLOCK_SIZE) * L2CAP_BUF_SIZE_MTUS_PER_CHANNEL; + + mp_bluetooth_nimble_l2cap_channel_t *chan = m_new_obj_var(mp_bluetooth_nimble_l2cap_channel_t, uint8_t, OS_MEMPOOL_SIZE(buf_blocks, L2CAP_BUF_BLOCK_SIZE) * sizeof(os_membuf_t)); + MP_STATE_PORT(bluetooth_nimble_root_pointers)->l2cap_chan = chan; + + // Will be set in BLE_L2CAP_EVENT_COC_CONNECTED or BLE_L2CAP_EVENT_COC_ACCEPT. + chan->chan = NULL; + + chan->mtu = mtu; + chan->rx_pending = NULL; + + int err = os_mempool_init(&chan->sdu_mempool, buf_blocks, L2CAP_BUF_BLOCK_SIZE, chan->sdu_mem, "l2cap_sdu_pool"); + if (err != 0) { + DEBUG_printf("mp_bluetooth_l2cap_connect: os_mempool_init failed %d\n", err); + return MP_ENOMEM; + } + + err = os_mbuf_pool_init(&chan->sdu_mbuf_pool, &chan->sdu_mempool, L2CAP_BUF_BLOCK_SIZE, buf_blocks); + if (err != 0) { + DEBUG_printf("mp_bluetooth_l2cap_connect: os_mbuf_pool_init failed %d\n", err); + return MP_ENOMEM; + } + + *out = chan; + return 0; +} + +int mp_bluetooth_l2cap_listen(uint16_t psm, uint16_t mtu) { + DEBUG_printf("mp_bluetooth_l2cap_listen: psm=%d, mtu=%d\n", psm, mtu); + + mp_bluetooth_nimble_l2cap_channel_t *chan; + int err = create_l2cap_channel(mtu, &chan); + if (err != 0) { + return err; + } + + MP_STATE_PORT(bluetooth_nimble_root_pointers)->l2cap_listening = true; + + return ble_hs_err_to_errno(ble_l2cap_create_server(psm, mtu, &l2cap_channel_event, chan)); +} + +int mp_bluetooth_l2cap_connect(uint16_t conn_handle, uint16_t psm, uint16_t mtu) { + DEBUG_printf("mp_bluetooth_l2cap_connect: conn_handle=%d, psm=%d, mtu=%d\n", conn_handle, psm, mtu); + + mp_bluetooth_nimble_l2cap_channel_t *chan; + int err = create_l2cap_channel(mtu, &chan); + if (err != 0) { + return err; + } + + struct os_mbuf *sdu_rx = os_mbuf_get_pkthdr(&chan->sdu_mbuf_pool, 0); + assert(sdu_rx); + return ble_hs_err_to_errno(ble_l2cap_connect(conn_handle, psm, mtu, sdu_rx, &l2cap_channel_event, chan)); +} + +int mp_bluetooth_l2cap_disconnect(uint16_t conn_handle, uint16_t cid) { + DEBUG_printf("mp_bluetooth_l2cap_disconnect: conn_handle=%d, cid=%d\n", conn_handle, cid); + mp_bluetooth_nimble_l2cap_channel_t *chan = get_l2cap_channel_for_conn_cid(conn_handle, cid); + if (!chan) { + return MP_EINVAL; + } + return ble_hs_err_to_errno(ble_l2cap_disconnect(chan->chan)); +} + +int mp_bluetooth_l2cap_send(uint16_t conn_handle, uint16_t cid, const uint8_t *buf, size_t len, bool *stalled) { + DEBUG_printf("mp_bluetooth_l2cap_send: conn_handle=%d, cid=%d, len=%d\n", conn_handle, cid, (int)len); + + mp_bluetooth_nimble_l2cap_channel_t *chan = get_l2cap_channel_for_conn_cid(conn_handle, cid); + if (!chan) { + return MP_EINVAL; + } + + struct ble_l2cap_chan_info info; + ble_l2cap_get_chan_info(chan->chan, &info); + if (len > info.peer_coc_mtu) { + // This is verified by ble_l2cap_send anyway, but this lets us + // avoid copying a too-large buffer into an mbuf. + return MP_EINVAL; + } + + if (len > (L2CAP_BUF_SIZE_MTUS_PER_CHANNEL - 1) * info.our_coc_mtu) { + // Always ensure there's at least one local MTU of space left in the buffer + // for the RX buffer. + return MP_EINVAL; + } + + // Grab an mbuf from the pool, and append the incoming buffer to it. + struct os_mbuf *sdu_tx = os_mbuf_get_pkthdr(&chan->sdu_mbuf_pool, 0); + if (sdu_tx == NULL) { + return MP_ENOMEM; + } + int err = os_mbuf_append(sdu_tx, buf, len); + if (err) { + os_mbuf_free_chain(sdu_tx); + return MP_ENOMEM; + } + + err = ble_l2cap_send(chan->chan, sdu_tx); + if (err == BLE_HS_ESTALLED) { + // Stalled means that this one will still send but any future ones + // will fail until we receive an unstalled event. + *stalled = true; + err = 0; + } else { + *stalled = false; + } + + // Other error codes such as BLE_HS_EBUSY (we're stalled) or BLE_HS_EBADDATA (bigger than MTU). + return ble_hs_err_to_errno(err); +} + +int mp_bluetooth_l2cap_recvinto(uint16_t conn_handle, uint16_t cid, uint8_t *buf, size_t *len) { + mp_bluetooth_nimble_l2cap_channel_t *chan = get_l2cap_channel_for_conn_cid(conn_handle, cid); + if (!chan) { + return MP_EINVAL; + } + + MICROPY_PY_BLUETOOTH_ENTER + if (chan->rx_pending) { + size_t avail = OS_MBUF_PKTLEN(chan->rx_pending); + + if (buf == NULL) { + // Can use this to implement a poll - just find out how much is available. + *len = avail; + } else { + // Have dest buffer and data available. + // Figure out how much we should copy. + *len = min(*len, avail); + + // Extract the required number of bytes. + os_mbuf_copydata(chan->rx_pending, 0, *len, buf); + + if (*len == avail) { + // That's all that's available -- free this mbuf and re-enable receiving. + os_mbuf_free_chain(chan->rx_pending); + chan->rx_pending = NULL; + + // We've already given the channel a new mbuf in l2cap_channel_event above, so + // re-use that mbuf in the call to ble_l2cap_recv_ready. This will just + // give the channel more credits. + struct os_mbuf *sdu_rx = chan->chan->coc_rx.sdu; + assert(sdu_rx); + if (sdu_rx) { + ble_l2cap_recv_ready(chan->chan, sdu_rx); + } + } else { + // Trim the used bytes from the start of the mbuf. + // Positive argument means "trim this many from head". + os_mbuf_adj(chan->rx_pending, *len); + // Clean up any empty mbufs at the head. + chan->rx_pending = os_mbuf_trim_front(chan->rx_pending); + } + } + } else { + // No pending data. + *len = 0; + } + + MICROPY_PY_BLUETOOTH_EXIT + return 0; +} + +#endif // MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS + #endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE diff --git a/extmod/nimble/modbluetooth_nimble.h b/extmod/nimble/modbluetooth_nimble.h index 7e401781e7f9c..9ed64368b2d5f 100644 --- a/extmod/nimble/modbluetooth_nimble.h +++ b/extmod/nimble/modbluetooth_nimble.h @@ -38,6 +38,12 @@ typedef struct _mp_bluetooth_nimble_root_pointers_t { // Pending service definitions. size_t n_services; struct ble_gatt_svc_def *services[MP_BLUETOOTH_NIMBLE_MAX_SERVICES]; + + #if MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS + // L2CAP channels. + struct _mp_bluetooth_nimble_l2cap_channel_t *l2cap_chan; + bool l2cap_listening; + #endif } mp_bluetooth_nimble_root_pointers_t; enum { diff --git a/extmod/nimble/nimble/nimble_npl_os.h b/extmod/nimble/nimble/nimble_npl_os.h index bfabe56e895c0..d0803f7e2e81d 100644 --- a/extmod/nimble/nimble/nimble_npl_os.h +++ b/extmod/nimble/nimble/nimble_npl_os.h @@ -30,12 +30,25 @@ // This is included by nimble/nimble_npl.h -- include that rather than this file directly. #include +#include // --- Configuration of NimBLE data structures -------------------------------- +// This is used at runtime to align allocations correctly. #define BLE_NPL_OS_ALIGNMENT (sizeof(uintptr_t)) #define BLE_NPL_TIME_FOREVER (0xffffffff) +// This is used at compile time to force struct member alignment. See +// os_mempool.h for where this is used (none of these three macros are defined +// by default). +#define OS_CFG_ALIGN_4 (4) +#define OS_CFG_ALIGN_8 (8) +#if (ULONG_MAX == 0xffffffffffffffff) +#define OS_CFG_ALIGNMENT (OS_CFG_ALIGN_8) +#else +#define OS_CFG_ALIGNMENT (OS_CFG_ALIGN_4) +#endif + typedef uint32_t ble_npl_time_t; typedef int32_t ble_npl_stime_t; diff --git a/extmod/nimble/syscfg/syscfg.h b/extmod/nimble/syscfg/syscfg.h index 8a6f2338be2ab..bef6b3b9210d4 100644 --- a/extmod/nimble/syscfg/syscfg.h +++ b/extmod/nimble/syscfg/syscfg.h @@ -99,9 +99,11 @@ int nimble_sprintf(char *str, const char *fmt, ...); #define MYNEWT_VAL_BLE_HS_PHONY_HCI_ACKS (0) #define MYNEWT_VAL_BLE_HS_REQUIRE_OS (1) #define MYNEWT_VAL_BLE_HS_STOP_ON_SHUTDOWN_TIMEOUT (2000) -#define MYNEWT_VAL_BLE_L2CAP_COC_MAX_NUM (0) +#define MYNEWT_VAL_BLE_L2CAP_COC_MAX_NUM (1) +#define MYNEWT_VAL_BLE_L2CAP_COC_MPS (MYNEWT_VAL_MSYS_1_BLOCK_SIZE - 8) +#define MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC (0) #define MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS (1) -#define MYNEWT_VAL_BLE_L2CAP_MAX_CHANS (3*MYNEWT_VAL_BLE_MAX_CONNECTIONS) +#define MYNEWT_VAL_BLE_L2CAP_MAX_CHANS (3 * MYNEWT_VAL_BLE_MAX_CONNECTIONS) #define MYNEWT_VAL_BLE_L2CAP_RX_FRAG_TIMEOUT (30000) #define MYNEWT_VAL_BLE_L2CAP_SIG_MAX_PROCS (1) #define MYNEWT_VAL_BLE_MONITOR_CONSOLE_BUFFER_SIZE (128) diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 75090a077e2bf..ced851ca38e27 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -499,6 +499,7 @@ endif endif ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) +CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS=1 include $(TOP)/extmod/nimble/nimble.mk SRC_C += mpnimbleport.c endif diff --git a/ports/unix/Makefile b/ports/unix/Makefile index f72c05f1add78..6a936a242545c 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -182,6 +182,7 @@ else # NimBLE is enabled. GIT_SUBMODULES += lib/mynewt-nimble +CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS=1 include $(TOP)/extmod/nimble/nimble.mk endif diff --git a/py/misc.h b/py/misc.h index aac90724460cf..fe2b3b8afaa75 100644 --- a/py/misc.h +++ b/py/misc.h @@ -53,6 +53,9 @@ typedef unsigned int uint; // Static assertion macro #define MP_STATIC_ASSERT(cond) ((void)sizeof(char[1 - 2 * !(cond)])) +// Round-up integer division +#define MP_CEIL_DIVIDE(a, b) (((a) + (b) - 1) / (b)) + /** memory allocation ******************************************/ // TODO make a lazy m_renew that can increase by a smaller amount than requested (but by at least 1 more element) From 3795c71271aa51902854911a20b020db6b2d274b Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 17 Nov 2020 23:30:06 +1100 Subject: [PATCH 088/179] docs/library/ubluetooth.rst: Add docs for L2CAP channels. Signed-off-by: Jim Mussared --- docs/library/ubluetooth.rst | 116 ++++++++++++++++++++++++++++++++++-- 1 file changed, 111 insertions(+), 5 deletions(-) diff --git a/docs/library/ubluetooth.rst b/docs/library/ubluetooth.rst index f94ad3a612c07..35ac13ad56fa9 100644 --- a/docs/library/ubluetooth.rst +++ b/docs/library/ubluetooth.rst @@ -6,8 +6,9 @@ This module provides an interface to a Bluetooth controller on a board. Currently this supports Bluetooth Low Energy (BLE) in Central, Peripheral, -Broadcaster, and Observer roles, as well as GATT Server and Client. A device -may operate in multiple roles concurrently. +Broadcaster, and Observer roles, as well as GATT Server and Client and L2CAP +connection-oriented-channels. A device may operate in multiple roles +concurrently. This API is intended to match the low-level Bluetooth protocol and provide building-blocks for higher-level abstractions such as specific device types. @@ -72,9 +73,9 @@ Configuration Increasing this allows better handling of bursty incoming data (for example scan results) and the ability to receive larger characteristic values. - - ``'mtu'``: Get/set the MTU that will be used during an MTU exchange. The + - ``'mtu'``: Get/set the MTU that will be used during a ATT MTU exchange. The resulting MTU will be the minimum of this and the remote device's MTU. - MTU exchange will not happen automatically (unless the remote device initiates + ATT MTU exchange will not happen automatically (unless the remote device initiates it), and must be manually initiated with :meth:`gattc_exchange_mtu`. Use the ``_IRQ_MTU_EXCHANGED`` event to discover the MTU for a given connection. @@ -179,8 +180,25 @@ Event Handling # Note: Status will be zero on successful acknowledgment, implementation-specific value otherwise. conn_handle, value_handle, status = data elif event == _IRQ_MTU_EXCHANGED: - # MTU exchange complete (either initiated by us or the remote device). + # ATT MTU exchange complete (either initiated by us or the remote device). conn_handle, mtu = data + elif event == _IRQ_L2CAP_ACCEPT: + # A new channel has been accepted. + # Return a non-zero integer to reject the connection, or zero (or None) to accept. + conn_handle, cid, psm, our_mtu, peer_mtu = data + elif event == _IRQ_L2CAP_CONNECT: + # A new channel is now connected (either as a result of connecting or accepting). + conn_handle, cid, psm, our_mtu, peer_mtu = data + elif event == _IRQ_L2CAP_DISCONNECT: + # Existing channel has disconnected (status is zero), or a connection attempt failed (non-zero status). + conn_handle, cid, psm, status = data + elif event == _IRQ_L2CAP_RECV: + # New data is available on the channel. Use l2cap_recvinto to read. + conn_handle, cid = data + elif event == _IRQ_L2CAP_SEND_READY: + # A previous l2cap_send that returned False has now completed and the channel is ready to send again. + # If status is non-zero, then the transmit buffer overflowed and the application should re-send the data. + conn_handle, cid, status = data The event codes are:: @@ -206,6 +224,11 @@ The event codes are:: _IRQ_GATTC_INDICATE = const(19) _IRQ_GATTS_INDICATE_DONE = const(20) _IRQ_MTU_EXCHANGED = const(21) + _IRQ_L2CAP_ACCEPT = const(22) + _IRQ_L2CAP_CONNECT = const(23) + _IRQ_L2CAP_DISCONNECT = const(24) + _IRQ_L2CAP_RECV = const(25) + _IRQ_L2CAP_SEND_READY = const(26) In order to save space in the firmware, these constants are not included on the :mod:`ubluetooth` module. Add the ones that you need from the list above to your @@ -493,6 +516,89 @@ device name from the device information service). peripheral initiating the MTU exchange. NimBLE works for both roles. +L2CAP connection-oriented-channels +---------------------------------- + + This feature allows for socket-like data exchange between two BLE devices. + Once the devices are connected via GAP, either device can listen for the + other to connect on a numeric PSM (Protocol/Service Multiplexer). + + **Note:** This is currently only supported when using the NimBLE stack on + STM32 and Unix (not ESP32). Only one L2CAP channel may be active at a given + time (i.e. you cannot connect while listening). + + Active L2CAP channels are identified by the connection handle that they were + established on and a CID (channel ID). + + Connection-oriented channels have built-in credit-based flow control. Unlike + ATT, where devices negotiate a shared MTU, both the listening and connecting + devices each set an independent MTU which limits the maximum amount of + outstanding data that the remote device can send before it is fully consumed + in :meth:`l2cap_recvinto `. + +.. method:: BLE.l2cap_listen(psm, mtu, /) + + Start listening for incoming L2CAP channel requests on the specified *psm* + with the local MTU set to *mtu*. + + When a remote device initiates a connection, the ``_IRQ_L2CAP_ACCEPT`` + event will be raised, which gives the listening server a chance to reject + the incoming connection (by returning a non-zero integer). + + Once the connection is accepted, the ``_IRQ_L2CAP_CONNECT`` event will be + raised, allowing the server to obtain the channel id (CID) and the local and + remote MTU. + + **Note:** It is not currently possible to stop listening. + +.. method:: BLE.l2cap_connect(conn_handle, psm, mtu, /) + + Connect to a listening peer on the specified *psm* with local MTU set to *mtu*. + + On successful connection, the the ``_IRQ_L2CAP_CONNECT`` event will be + raised, allowing the client to obtain the CID and the local and remote (peer) MTU. + + An unsuccessful connection will raise the ``_IRQ_L2CAP_DISCONNECT`` event + with a non-zero status. + +.. method:: BLE.l2cap_disconnect(conn_handle, cid, /) + + Disconnect an active L2CAP channel with the specified *conn_handle* and + *cid*. + +.. method:: BLE.l2cap_send(conn_handle, cid, buf, /) + + Send the specified *buf* (which must support the buffer protocol) on the + L2CAP channel identified by *conn_handle* and *cid*. + + The specified buffer cannot be larger than the remote (peer) MTU, and no + more than twice the size of the local MTU. + + This will return ``False`` if the channel is now "stalled", which means that + :meth:`l2cap_send ` must not be called again until the + ``_IRQ_L2CAP_SEND_READY`` event is received (which will happen when the + remote device grants more credits, typically after it has received and + processed the data). + +.. method:: BLE.l2cap_recvinto(conn_handle, cid, buf, /) + + Receive data from the specified *conn_handle* and *cid* into the provided + *buf* (which must support the buffer protocol, e.g. bytearray or + memoryview). + + Returns the number of bytes read from the channel. + + If *buf* is None, then returns the number of bytes available. + + **Note:** After receiving the ``_IRQ_L2CAP_RECV`` event, the application should + continue calling :meth:`l2cap_recvinto ` until no more + bytes are available in the receive buffer (typically up to the size of the + remote (peer) MTU). + + Until the receive buffer is empty, the remote device will not be granted + more channel credits and will be unable to send any more data. + + class UUID ---------- From 23fad2526db2bbc8b6b07bfd67cc6feba1770249 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 17 Nov 2020 23:30:23 +1100 Subject: [PATCH 089/179] tests/multi_bluetooth: Add L2CAP channels multi-test. Signed-off-by: Jim Mussared --- tests/multi_bluetooth/ble_l2cap.py | 175 +++++++++++++++++++++++++ tests/multi_bluetooth/ble_l2cap.py.exp | 23 ++++ 2 files changed, 198 insertions(+) create mode 100644 tests/multi_bluetooth/ble_l2cap.py create mode 100644 tests/multi_bluetooth/ble_l2cap.py.exp diff --git a/tests/multi_bluetooth/ble_l2cap.py b/tests/multi_bluetooth/ble_l2cap.py new file mode 100644 index 0000000000000..a26f59b3ef0f9 --- /dev/null +++ b/tests/multi_bluetooth/ble_l2cap.py @@ -0,0 +1,175 @@ +# Test L2CAP COC send/recv. + +# Sends a sequence of varying-sized payloads from central->peripheral, and +# verifies that the other device sees the same data, then does the same thing +# peripheral->central. + +from micropython import const +import time, machine, bluetooth, random + +TIMEOUT_MS = 1000 + +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_L2CAP_ACCEPT = const(22) +_IRQ_L2CAP_CONNECT = const(23) +_IRQ_L2CAP_DISCONNECT = const(24) +_IRQ_L2CAP_RECV = const(25) +_IRQ_L2CAP_SEND_READY = const(26) + +_L2CAP_MTU = const(450) +_L2CAP_PSM = const(22) + +_PAYLOAD_LEN = const(_L2CAP_MTU - 50) +_PAYLOAD_LEN_STEP = -23 +_NUM_PAYLOADS = const(16) + +_RANDOM_SEED = 22 + + +waiting_events = {} + + +def irq(event, data): + if event == _IRQ_CENTRAL_CONNECT: + conn_handle, addr_type, addr = data + print("_IRQ_CENTRAL_CONNECT") + waiting_events[event] = conn_handle + elif event == _IRQ_CENTRAL_DISCONNECT: + print("_IRQ_CENTRAL_DISCONNECT") + elif event == _IRQ_PERIPHERAL_CONNECT: + conn_handle, addr_type, addr = data + print("_IRQ_PERIPHERAL_CONNECT") + waiting_events[event] = conn_handle + elif event == _IRQ_PERIPHERAL_DISCONNECT: + print("_IRQ_PERIPHERAL_DISCONNECT") + elif event == _IRQ_L2CAP_ACCEPT: + conn_handle, cid, psm, our_mtu, peer_mtu = data + print("_IRQ_L2CAP_ACCEPT", psm, our_mtu, peer_mtu) + waiting_events[event] = (conn_handle, cid, psm) + elif event == _IRQ_L2CAP_CONNECT: + conn_handle, cid, psm, our_mtu, peer_mtu = data + print("_IRQ_L2CAP_CONNECT", psm, our_mtu, peer_mtu) + waiting_events[event] = (conn_handle, cid, psm, our_mtu, peer_mtu) + elif event == _IRQ_L2CAP_DISCONNECT: + conn_handle, cid, psm, status = data + print("_IRQ_L2CAP_DISCONNECT", psm, status) + elif event == _IRQ_L2CAP_RECV: + conn_handle, cid = data + elif event == _IRQ_L2CAP_SEND_READY: + conn_handle, cid, status = data + + if event not in waiting_events: + waiting_events[event] = None + + +def wait_for_event(event, timeout_ms): + t0 = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: + if event in waiting_events: + return waiting_events.pop(event) + machine.idle() + raise ValueError("Timeout waiting for {}".format(event)) + + +def send_data(ble, conn_handle, cid): + buf = bytearray(_PAYLOAD_LEN) + mv = memoryview(buf) + print("l2cap_send", _NUM_PAYLOADS, _PAYLOAD_LEN) + for i in range(_NUM_PAYLOADS): + n = _PAYLOAD_LEN + i * _PAYLOAD_LEN_STEP + for j in range(n): + buf[j] = random.randint(0, 255) + if not ble.l2cap_send(conn_handle, cid, mv[:n]): + wait_for_event(_IRQ_L2CAP_SEND_READY, TIMEOUT_MS) + + +def recv_data(ble, conn_handle, cid): + buf = bytearray(_PAYLOAD_LEN) + recv_bytes = 0 + recv_correct = 0 + expected_bytes = ( + _PAYLOAD_LEN * _NUM_PAYLOADS + _PAYLOAD_LEN_STEP * _NUM_PAYLOADS * (_NUM_PAYLOADS - 1) // 2 + ) + print("l2cap_recvinto", expected_bytes) + while recv_bytes < expected_bytes: + wait_for_event(_IRQ_L2CAP_RECV, TIMEOUT_MS) + while True: + n = ble.l2cap_recvinto(conn_handle, cid, buf) + if n == 0: + break + recv_bytes += n + for i in range(n): + if buf[i] == random.randint(0, 255): + recv_correct += 1 + return recv_bytes, recv_correct + + +# Acting in peripheral role. +def instance0(): + multitest.globals(BDADDR=ble.config("mac")) + print("gap_advertise") + ble.gap_advertise(20_000, b"\x02\x01\x06\x04\xffMPY") + multitest.next() + try: + # Wait for central to connect to us. + conn_handle = wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) + + print("l2cap_listen") + ble.l2cap_listen(_L2CAP_PSM, _L2CAP_MTU) + + conn_handle, cid, psm = wait_for_event(_IRQ_L2CAP_ACCEPT, TIMEOUT_MS) + conn_handle, cid, psm, our_mtu, peer_mtu = wait_for_event(_IRQ_L2CAP_CONNECT, TIMEOUT_MS) + + random.seed(_RANDOM_SEED) + + recv_bytes, recv_correct = recv_data(ble, conn_handle, cid) + send_data(ble, conn_handle, cid) + + wait_for_event(_IRQ_L2CAP_DISCONNECT, TIMEOUT_MS) + + print("received", recv_bytes, recv_correct) + + # Wait for the central to disconnect. + wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS) + finally: + ble.active(0) + + +# Acting in central role. +def instance1(): + multitest.next() + try: + # Connect to peripheral and then disconnect. + print("gap_connect") + ble.gap_connect(*BDADDR) + conn_handle = wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) + + print("l2cap_connect") + ble.l2cap_connect(conn_handle, _L2CAP_PSM, _L2CAP_MTU) + conn_handle, cid, psm, our_mtu, peer_mtu = wait_for_event(_IRQ_L2CAP_CONNECT, TIMEOUT_MS) + + random.seed(_RANDOM_SEED) + + send_data(ble, conn_handle, cid) + recv_bytes, recv_correct = recv_data(ble, conn_handle, cid) + + # Disconnect channel. + print("l2cap_disconnect") + ble.l2cap_disconnect(conn_handle, cid) + wait_for_event(_IRQ_L2CAP_DISCONNECT, TIMEOUT_MS) + + print("received", recv_bytes, recv_correct) + + # Disconnect from peripheral. + print("gap_disconnect:", ble.gap_disconnect(conn_handle)) + wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS) + finally: + ble.active(0) + + +ble = bluetooth.BLE() +ble.active(1) +ble.irq(irq) diff --git a/tests/multi_bluetooth/ble_l2cap.py.exp b/tests/multi_bluetooth/ble_l2cap.py.exp new file mode 100644 index 0000000000000..0c572d99fefb1 --- /dev/null +++ b/tests/multi_bluetooth/ble_l2cap.py.exp @@ -0,0 +1,23 @@ +--- instance0 --- +gap_advertise +_IRQ_CENTRAL_CONNECT +l2cap_listen +_IRQ_L2CAP_ACCEPT 22 450 450 +_IRQ_L2CAP_CONNECT 22 450 450 +l2cap_recvinto 3640 +l2cap_send 16 400 +_IRQ_L2CAP_DISCONNECT 22 0 +received 3640 3640 +_IRQ_CENTRAL_DISCONNECT +--- instance1 --- +gap_connect +_IRQ_PERIPHERAL_CONNECT +l2cap_connect +_IRQ_L2CAP_CONNECT 22 450 450 +l2cap_send 16 400 +l2cap_recvinto 3640 +l2cap_disconnect +_IRQ_L2CAP_DISCONNECT 22 0 +received 3640 3640 +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT From 5a7027915c3b9ec2dffc3af9530dad90b56b8cc0 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 24 Nov 2020 07:56:34 +1100 Subject: [PATCH 090/179] extmod/nimble/modbluetooth_nimble: Fix build when l2cap unavailable. Signed-off-by: Jim Mussared --- extmod/nimble/modbluetooth_nimble.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index fa416ebc3f529..bbff26b5354e9 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -42,9 +42,11 @@ #include "services/gap/ble_svc_gap.h" #include "services/gatt/ble_svc_gatt.h" +#if MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS // We need the definition of "struct ble_l2cap_chan". // See l2cap_channel_event() for details. #include "nimble/host/src/ble_l2cap_priv.h" +#endif #ifndef MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME #define MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME "MPY NIMBLE" From 6a3d70db9642e29b017f111abaf7fb6238a9b5a6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 28 Nov 2020 12:21:40 +1100 Subject: [PATCH 091/179] tests/extmod: Add vfs_posix.py test for uos.VfsPosix class. Signed-off-by: Damien George --- tests/extmod/vfs_posix.py | 23 +++++++++++++++++++++++ tests/extmod/vfs_posix.py.exp | 4 ++++ 2 files changed, 27 insertions(+) create mode 100644 tests/extmod/vfs_posix.py create mode 100644 tests/extmod/vfs_posix.py.exp diff --git a/tests/extmod/vfs_posix.py b/tests/extmod/vfs_posix.py new file mode 100644 index 0000000000000..3bea99365d970 --- /dev/null +++ b/tests/extmod/vfs_posix.py @@ -0,0 +1,23 @@ +# Test for VfsPosix + +try: + import uos + + uos.VfsPosix +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + + +# getcwd and chdir +curdir = uos.getcwd() +uos.chdir("/") +print(uos.getcwd()) +uos.chdir(curdir) +print(uos.getcwd() == curdir) + +# stat +print(type(uos.stat("/"))) + +# listdir and ilistdir +print(type(uos.listdir("/"))) diff --git a/tests/extmod/vfs_posix.py.exp b/tests/extmod/vfs_posix.py.exp new file mode 100644 index 0000000000000..1b1f59b43e5da --- /dev/null +++ b/tests/extmod/vfs_posix.py.exp @@ -0,0 +1,4 @@ +/ +True + + From be24e6a53faf31926a65c51426591b21f3f09c54 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 28 Nov 2020 17:31:13 +1100 Subject: [PATCH 092/179] py/mpprint: Prevent case fall-through when assert is disabled. Signed-off-by: Damien George --- py/mpprint.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/py/mpprint.c b/py/mpprint.c index c550c1d952f0a..31cf73bb58af9 100644 --- a/py/mpprint.c +++ b/py/mpprint.c @@ -557,11 +557,9 @@ int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args) { case 'l': { unsigned long long int arg_value = va_arg(args, unsigned long long int); ++fmt; - if (*fmt == 'u' || *fmt == 'd') { - chrs += mp_print_int(print, arg_value, *fmt == 'd', 10, 'a', flags, fill, width); - break; - } - assert(!"unsupported fmt char"); + assert(*fmt == 'u' || *fmt == 'd' || !"unsupported fmt char"); + chrs += mp_print_int(print, arg_value, *fmt == 'd', 10, 'a', flags, fill, width); + break; } #endif default: From 547e8a9fe70dee4de92a35b7a98a10bf26a20321 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 29 Nov 2020 18:04:35 +1100 Subject: [PATCH 093/179] tools/ci.sh: Add helper script to run CI tasks. The aim is for this script to be used on any CI platform, as well as run locally. Signed-off-by: Damien George --- tools/ci.sh | 461 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 461 insertions(+) create mode 100755 tools/ci.sh diff --git a/tools/ci.sh b/tools/ci.sh new file mode 100755 index 0000000000000..3b906b449c112 --- /dev/null +++ b/tools/ci.sh @@ -0,0 +1,461 @@ +#!/bin/bash + +if which nproc > /dev/null; then + MAKEOPTS="-j$(nproc)" +else + MAKEOPTS="-j$(sysctl -n hw.ncpu)" +fi + +######################################################################################## +# general helper functions + +function ci_gcc_arm_setup { + sudo apt-get install gcc-arm-none-eabi libnewlib-arm-none-eabi + arm-none-eabi-gcc --version +} + +######################################################################################## +# code formatting + +function ci_code_formatting_setup { + sudo apt-add-repository --yes --update ppa:pybricks/ppa + sudo apt-get install uncrustify + pip3 install black + uncrustify --version + black --version +} + +function ci_code_formatting_run { + tools/codeformat.py -v +} + +######################################################################################## +# code size + +function ci_code_size_setup { + sudo apt-get install gcc-multilib + gcc --version + ci_gcc_arm_setup +} + +function ci_code_size_build { + # starts off at either the ref/pull/N/merge FETCH_HEAD, or the current branch HEAD + git checkout -b pull_request # save the current location + git remote add upstream https://github.com/micropython/micropython.git + git fetch --depth=100 upstream + # build reference, save to size0 + # ignore any errors with this build, in case master is failing + git checkout `git merge-base --fork-point upstream/master pull_request` + git show -s + tools/metrics.py clean bm + tools/metrics.py build bm | tee ~/size0 || true + # build PR/branch, save to size1 + git checkout pull_request + git log upstream/master..HEAD + tools/metrics.py clean bm + tools/metrics.py build bm | tee ~/size1 +} + +######################################################################################## +# ports/cc3200 + +function ci_cc3200_setup { + ci_gcc_arm_setup +} + +function ci_cc3200_build { + make ${MAKEOPTS} -C ports/cc3200 BTARGET=application BTYPE=release + make ${MAKEOPTS} -C ports/cc3200 BTARGET=bootloader BTYPE=release +} + +######################################################################################## +# ports/esp32 + +function ci_esp32_idf3_setup { + sudo pip3 install pyserial 'pyparsing<2.4' + curl -L https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz | tar zxf - + git clone https://github.com/espressif/esp-idf.git + echo $(pwd)/xtensa-esp32-elf/bin +} + +function ci_esp32_idf3_build { + make ${MAKEOPTS} -C mpy-cross + git -C esp-idf checkout $(grep "ESPIDF_SUPHASH_V3 :=" ports/esp32/Makefile | cut -d " " -f 3) + git -C esp-idf submodule update --init components/json/cJSON components/esp32/lib components/esptool_py/esptool components/expat/expat components/lwip/lwip components/mbedtls/mbedtls components/micro-ecc/micro-ecc components/nghttp/nghttp2 components/nimble components/bt + make ${MAKEOPTS} -C ports/esp32 submodules + make ${MAKEOPTS} -C ports/esp32 +} + +function ci_esp32_idf4_setup { + sudo pip3 install pyserial 'pyparsing<2.4' + curl -L https://dl.espressif.com/dl/xtensa-esp32-elf-gcc8_2_0-esp-2019r2-linux-amd64.tar.gz | tar zxf - + git clone https://github.com/espressif/esp-idf.git + echo $(pwd)/xtensa-esp32-elf/bin +} + +function ci_esp32_idf4_build { + make ${MAKEOPTS} -C mpy-cross + git -C esp-idf checkout $(grep "ESPIDF_SUPHASH_V4 :=" ports/esp32/Makefile | cut -d " " -f 3) + git -C esp-idf submodule update --init components/bt/controller/lib components/bt/host/nimble/nimble components/esp_wifi/lib_esp32 components/esptool_py/esptool components/lwip/lwip components/mbedtls/mbedtls + make ${MAKEOPTS} -C ports/esp32 submodules + make ${MAKEOPTS} -C ports/esp32 +} + +######################################################################################## +# ports/esp8266 + +function ci_esp8266_setup { + sudo pip install pyserial + wget https://github.com/jepler/esp-open-sdk/releases/download/2018-06-10/xtensa-lx106-elf-standalone.tar.gz + zcat xtensa-lx106-elf-standalone.tar.gz | tar x + echo $(pwd)/xtensa-lx106-elf/bin +} + +function ci_esp8266_build { + make ${MAKEOPTS} -C mpy-cross + make ${MAKEOPTS} -C ports/esp8266 submodules + make ${MAKEOPTS} -C ports/esp8266 + make ${MAKEOPTS} -C ports/esp8266 BOARD=GENERIC_512K + make ${MAKEOPTS} -C ports/esp8266 BOARD=GENERIC_1M +} + +######################################################################################## +# ports/nrf + +function ci_nrf_setup { + ci_gcc_arm_setup +} + +function ci_nrf_build { + ports/nrf/drivers/bluetooth/download_ble_stack.sh s140_nrf52_6_1_1 + make ${MAKEOPTS} -C ports/nrf submodules + make ${MAKEOPTS} -C ports/nrf BOARD=pca10040 + make ${MAKEOPTS} -C ports/nrf BOARD=microbit + make ${MAKEOPTS} -C ports/nrf BOARD=pca10056 SD=s140 + make ${MAKEOPTS} -C ports/nrf BOARD=pca10090 +} + +######################################################################################## +# ports/powerpc + +function ci_powerpc_setup { + sudo apt-get install gcc-powerpc64le-linux-gnu libc6-dev-ppc64el-cross +} + +function ci_powerpc_build { + make ${MAKEOPTS} -C ports/powerpc UART=potato + make ${MAKEOPTS} -C ports/powerpc UART=lpc_serial +} + +######################################################################################## +# ports/qemu-arm + +function ci_qemu_arm_setup { + ci_gcc_arm_setup + sudo apt-get update + sudo apt-get install qemu-system + qemu-system-arm --version +} + +function ci_qemu_arm_build { + make ${MAKEOPTS} -C mpy-cross + make ${MAKEOPTS} -C ports/qemu-arm CFLAGS_EXTRA=-DMP_ENDIANNESS_BIG=1 + make ${MAKEOPTS} -C ports/qemu-arm clean + make ${MAKEOPTS} -C ports/qemu-arm -f Makefile.test test +} + +######################################################################################## +# ports/samd + +function ci_samd_setup { + ci_gcc_arm_setup +} + +function ci_samd_build { + make ${MAKEOPTS} -C ports/samd submodules + make ${MAKEOPTS} -C ports/samd +} + +######################################################################################## +# ports/stm32 + +function ci_stm32_setup { + ci_gcc_arm_setup +} + +function ci_stm32_pyb_build { + make ${MAKEOPTS} -C mpy-cross + make ${MAKEOPTS} -C ports/stm32 submodules + git submodule update --init lib/btstack + make ${MAKEOPTS} -C ports/stm32 BOARD=PYBV11 MICROPY_PY_WIZNET5K=5200 MICROPY_PY_CC3K=1 USER_C_MODULES=../../examples/usercmodule CFLAGS_EXTRA="-DMODULE_CEXAMPLE_ENABLED=1 -DMODULE_CPPEXAMPLE_ENABLED=1" + make ${MAKEOPTS} -C ports/stm32 BOARD=PYBD_SF2 + make ${MAKEOPTS} -C ports/stm32 BOARD=PYBD_SF6 NANBOX=1 MICROPY_BLUETOOTH_NIMBLE=0 MICROPY_BLUETOOTH_BTSTACK=1 + make ${MAKEOPTS} -C ports/stm32/mboot BOARD=PYBV10 CFLAGS_EXTRA='-DMBOOT_FSLOAD=1 -DMBOOT_VFS_LFS2=1' + make ${MAKEOPTS} -C ports/stm32/mboot BOARD=PYBD_SF6 +} + +function ci_stm32_nucleo_build { + make ${MAKEOPTS} -C mpy-cross + make ${MAKEOPTS} -C ports/stm32 submodules + make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_F091RC + make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_H743ZI CFLAGS_EXTRA='-DMICROPY_PY_THREAD=1' + make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_L073RZ + make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_L476RG + make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_WB55 + make ${MAKEOPTS} -C ports/stm32/mboot BOARD=NUCLEO_WB55 +} + +######################################################################################## +# ports/teensy + +function ci_teensy_setup { + ci_gcc_arm_setup +} + +function ci_teensy_build { + make ${MAKEOPTS} -C ports/teensy +} + +######################################################################################## +# ports/unix + +CI_UNIX_OPTS_SYS_SETTRACE=( + MICROPY_PY_BTREE=0 + MICROPY_PY_FFI=0 + MICROPY_PY_USSL=0 + CFLAGS_EXTRA="-DMICROPY_PY_SYS_SETTRACE=1" +) + +CI_UNIX_OPTS_SYS_SETTRACE_STACKLESS=( + MICROPY_PY_BTREE=0 + MICROPY_PY_FFI=0 + MICROPY_PY_USSL=0 + CFLAGS_EXTRA="-DMICROPY_STACKLESS=1 -DMICROPY_STACKLESS_STRICT=1 -DMICROPY_PY_SYS_SETTRACE=1" +) + +function ci_unix_build_helper { + make ${MAKEOPTS} -C mpy-cross + make ${MAKEOPTS} -C ports/unix "$@" submodules + make ${MAKEOPTS} -C ports/unix "$@" deplibs + make ${MAKEOPTS} -C ports/unix "$@" +} + +function ci_unix_run_tests_helper { + make -C ports/unix "$@" test +} + +function ci_unix_run_tests_full_helper { + variant=$1 + shift + if [ $variant = standard ]; then + micropython=micropython + else + micropython=micropython-$variant + fi + make -C ports/unix VARIANT=$variant "$@" test_full + (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=../ports/unix/$micropython ./run-multitests.py multi_net/*.py) +} + +function ci_native_mpy_modules_build { + if [ "$1" = "" ]; then + arch=x64 + else + arch=$1 + fi + make -C examples/natmod/features1 ARCH=$arch + make -C examples/natmod/features2 ARCH=$arch + make -C examples/natmod/btree ARCH=$arch + make -C examples/natmod/framebuf ARCH=$arch + make -C examples/natmod/uheapq ARCH=$arch + make -C examples/natmod/urandom ARCH=$arch + make -C examples/natmod/ure ARCH=$arch + make -C examples/natmod/uzlib ARCH=$arch +} + +function ci_native_mpy_modules_32bit_build { + ci_native_mpy_modules_build x86 +} + +function ci_unix_minimal_build { + make ${MAKEOPTS} -C ports/unix VARIANT=minimal +} + +function ci_unix_minimal_run_tests { + (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=../ports/unix/micropython-minimal ./run-tests -e exception_chain -e self_type_check -e subclass_native_init -d basics) +} + +function ci_unix_standard_build { + ci_unix_build_helper VARIANT=standard +} + +function ci_unix_standard_run_tests { + ci_unix_run_tests_full_helper standard +} + +function ci_unix_standard_run_perfbench { + (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=../ports/unix/micropython ./run-perfbench.py 1000 1000) +} + +function ci_unix_coverage_setup { + sudo apt-get install lcov + sudo pip3 install setuptools + sudo pip3 install pyelftools + gcc --version + python3 --version +} + +function ci_unix_coverage_build { + ci_unix_build_helper VARIANT=coverage +} + +function ci_unix_coverage_run_tests { + ci_unix_run_tests_full_helper coverage +} + +function ci_unix_coverage_run_native_mpy_tests { + MICROPYPATH=examples/natmod/features2 ./ports/unix/micropython-coverage -m features2 + (cd tests && ./run-natmodtests.py "$@" extmod/{btree*,framebuf*,uheapq*,ure*,uzlib*}.py) +} + +function ci_unix_32bit_setup { + sudo dpkg --add-architecture i386 + sudo apt-get update + sudo apt-get install gcc-multilib g++-multilib libffi-dev:i386 + sudo pip3 install setuptools + sudo pip3 install pyelftools + gcc --version + python2 --version + python3 --version +} + +function ci_unix_coverage_32bit_build { + ci_unix_build_helper VARIANT=coverage MICROPY_FORCE_32BIT=1 +} + +function ci_unix_coverage_32bit_run_tests { + ci_unix_run_tests_full_helper coverage MICROPY_FORCE_32BIT=1 +} + +function ci_unix_coverage_32bit_run_native_mpy_tests { + ci_unix_coverage_run_native_mpy_tests --arch x86 +} + +function ci_unix_nanbox_build { + # Use Python 2 to check that it can run the build scripts + ci_unix_build_helper PYTHON=python2 VARIANT=nanbox +} + +function ci_unix_nanbox_run_tests { + ci_unix_run_tests_full_helper nanbox PYTHON=python2 +} + +function ci_unix_float_build { + ci_unix_build_helper VARIANT=standard CFLAGS_EXTRA="-DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_FLOAT" +} + +function ci_unix_float_run_tests { + # TODO get this working: ci_unix_run_tests_full_helper standard CFLAGS_EXTRA="-DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_FLOAT" + ci_unix_run_tests_helper CFLAGS_EXTRA="-DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_FLOAT" +} + +function ci_unix_clang_setup { + sudo apt-get install clang + clang --version +} + +function ci_unix_stackless_clang_build { + make ${MAKEOPTS} -C mpy-cross CC=clang + make ${MAKEOPTS} -C ports/unix submodules + make ${MAKEOPTS} -C ports/unix CC=clang CFLAGS_EXTRA="-DMICROPY_STACKLESS=1 -DMICROPY_STACKLESS_STRICT=1" +} + +function ci_unix_stackless_clang_run_tests { + ci_unix_run_tests_helper CC=clang +} + +function ci_unix_float_clang_build { + make ${MAKEOPTS} -C mpy-cross CC=clang + make ${MAKEOPTS} -C ports/unix submodules + make ${MAKEOPTS} -C ports/unix CC=clang CFLAGS_EXTRA="-DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_FLOAT" +} + +function ci_unix_float_clang_run_tests { + ci_unix_run_tests_helper CC=clang +} + +function ci_unix_settrace_build { + make ${MAKEOPTS} -C mpy-cross + make ${MAKEOPTS} -C ports/unix "${CI_UNIX_OPTS_SYS_SETTRACE[@]}" +} + +function ci_unix_settrace_run_tests { + ci_unix_run_tests_helper "${CI_UNIX_OPTS_SYS_SETTRACE[@]}" +} + +function ci_unix_settrace_stackless_build { + make ${MAKEOPTS} -C mpy-cross + make ${MAKEOPTS} -C ports/unix "${CI_UNIX_OPTS_SYS_SETTRACE_STACKLESS[@]}" +} + +function ci_unix_settrace_stackless_run_tests { + ci_unix_run_tests_helper "${CI_UNIX_OPTS_SYS_SETTRACE_STACKLESS[@]}" +} + +function ci_unix_macos_build { + make ${MAKEOPTS} -C mpy-cross + make ${MAKEOPTS} -C ports/unix submodules + #make ${MAKEOPTS} -C ports/unix deplibs + make ${MAKEOPTS} -C ports/unix + # check for additional compiler errors/warnings + make ${MAKEOPTS} -C ports/unix VARIANT=coverage submodules + make ${MAKEOPTS} -C ports/unix VARIANT=coverage +} + +function ci_unix_macos_run_tests { + # Issues with macOS tests: + # - OSX has poor time resolution and these uasyncio tests do not have correct output + # - import_pkg7 has a problem with relative imports + # - urandom_basic has a problem with getrandbits(0) + (cd tests && ./run-tests --exclude 'uasyncio_(basic|heaplock|lock|wait_task)' --exclude 'import_pkg7.py' --exclude 'urandom_basic.py') +} + +######################################################################################## +# ports/windows + +function ci_windows_setup { + sudo apt-get install gcc-mingw-w64 +} + +function ci_windows_build { + make ${MAKEOPTS} -C mpy-cross + make ${MAKEOPTS} -C ports/windows CROSS_COMPILE=i686-w64-mingw32- +} + +######################################################################################## +# ports/zephyr + +function ci_zephyr_setup { + docker pull zephyrprojectrtos/ci:v0.11.8 + docker run --name zephyr-ci -d -it \ + -v "$(pwd)":/micropython \ + -e ZEPHYR_SDK_INSTALL_DIR=/opt/sdk/zephyr-sdk-0.11.3 \ + -e ZEPHYR_TOOLCHAIN_VARIANT=zephyr \ + -w /micropython/ports/zephyr \ + zephyrprojectrtos/ci:v0.11.8 + docker ps -a +} + +function ci_zephyr_install { + docker exec zephyr-ci west init --mr v2.4.0 /zephyrproject + docker exec -w /zephyrproject zephyr-ci west update + docker exec -w /zephyrproject zephyr-ci west zephyr-export +} + +function ci_zephyr_build { + docker exec zephyr-ci bash -c "make clean; ./make-minimal ${MAKEOPTS}" + docker exec zephyr-ci bash -c "make clean; ./make-minimal ${MAKEOPTS} BOARD=frdm_k64f" + docker exec zephyr-ci bash -c "make clean; make ${MAKEOPTS}" + docker exec zephyr-ci bash -c "make clean; make ${MAKEOPTS} BOARD=frdm_k64f" + docker exec zephyr-ci bash -c "make clean; make ${MAKEOPTS} BOARD=mimxrt1050_evk" + docker exec zephyr-ci bash -c "make clean; make ${MAKEOPTS} BOARD=reel_board" +} From a598ae5b4dc4054f90156480454689c79f7c41a8 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 29 Nov 2020 18:05:45 +1100 Subject: [PATCH 094/179] github/workflows: Add workflows for all CI tasks, builds and tests. Signed-off-by: Damien George --- .github/workflows/code_formatting.yml | 16 +++ .github/workflows/code_size.yml | 25 ++++ .github/workflows/ports_cc3200.yml | 23 ++++ .github/workflows/ports_esp32.yml | 36 ++++++ .github/workflows/ports_esp8266.yml | 23 ++++ .github/workflows/ports_nrf.yml | 23 ++++ .github/workflows/ports_powerpc.yml | 23 ++++ .github/workflows/ports_qemu-arm.yml | 26 ++++ .github/workflows/ports_samd.yml | 23 ++++ .github/workflows/ports_stm32.yml | 32 +++++ .github/workflows/ports_teensy.yml | 23 ++++ .github/workflows/ports_unix.yml | 176 ++++++++++++++++++++++++++ .github/workflows/ports_windows.yml | 23 ++++ .github/workflows/ports_zephyr.yml | 24 ++++ 14 files changed, 496 insertions(+) create mode 100644 .github/workflows/code_formatting.yml create mode 100644 .github/workflows/code_size.yml create mode 100644 .github/workflows/ports_cc3200.yml create mode 100644 .github/workflows/ports_esp32.yml create mode 100644 .github/workflows/ports_esp8266.yml create mode 100644 .github/workflows/ports_nrf.yml create mode 100644 .github/workflows/ports_powerpc.yml create mode 100644 .github/workflows/ports_qemu-arm.yml create mode 100644 .github/workflows/ports_samd.yml create mode 100644 .github/workflows/ports_stm32.yml create mode 100644 .github/workflows/ports_teensy.yml create mode 100644 .github/workflows/ports_unix.yml create mode 100644 .github/workflows/ports_windows.yml create mode 100644 .github/workflows/ports_zephyr.yml diff --git a/.github/workflows/code_formatting.yml b/.github/workflows/code_formatting.yml new file mode 100644 index 0000000000000..aab347d78e97d --- /dev/null +++ b/.github/workflows/code_formatting.yml @@ -0,0 +1,16 @@ +name: Check code formatting + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v1 + - name: Install packages + run: source tools/ci.sh && ci_code_formatting_setup + - name: Run code formatting + run: source tools/ci.sh && ci_code_formatting_run + - name: Check code formatting + run: git diff --exit-code diff --git a/.github/workflows/code_size.yml b/.github/workflows/code_size.yml new file mode 100644 index 0000000000000..dd759a4f3ff56 --- /dev/null +++ b/.github/workflows/code_size.yml @@ -0,0 +1,25 @@ +name: Check code size + +on: + push: + pull_request: + paths: + - '.github/workflows/*.yml' + - 'tools/**' + - 'py/**' + - 'extmod/**' + - 'lib/**' + - 'ports/bare-arm/**' + - 'ports/minimal/**' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install packages + run: source tools/ci.sh && ci_code_size_setup + - name: Build + run: source tools/ci.sh && ci_code_size_build + - name: Compute code size difference + run: tools/metrics.py diff --error-threshold 0 ~/size0 ~/size1 diff --git a/.github/workflows/ports_cc3200.yml b/.github/workflows/ports_cc3200.yml new file mode 100644 index 0000000000000..0eaa36da3796e --- /dev/null +++ b/.github/workflows/ports_cc3200.yml @@ -0,0 +1,23 @@ +name: cc3200 port + +on: + push: + pull_request: + paths: + - '.github/workflows/*.yml' + - 'tools/**' + - 'py/**' + - 'extmod/**' + - 'lib/**' + - 'drivers/**' + - 'ports/cc3200/**' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install packages + run: source tools/ci.sh && ci_cc3200_setup + - name: Build + run: source tools/ci.sh && ci_cc3200_build diff --git a/.github/workflows/ports_esp32.yml b/.github/workflows/ports_esp32.yml new file mode 100644 index 0000000000000..b89e462e049fc --- /dev/null +++ b/.github/workflows/ports_esp32.yml @@ -0,0 +1,36 @@ +name: esp32 port + +on: + push: + pull_request: + paths: + - '.github/workflows/*.yml' + - 'tools/**' + - 'py/**' + - 'extmod/**' + - 'lib/**' + - 'drivers/**' + - 'ports/esp32/**' + +jobs: + idf3_build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Install packages + run: source tools/ci.sh && ci_esp32_idf3_setup >> $GITHUB_PATH + - name: Build + env: + IDF_PATH: ${{ github.workspace }}/esp-idf + run: source tools/ci.sh && ci_esp32_idf3_build + + idf4_build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Install packages + run: source tools/ci.sh && ci_esp32_idf4_setup >> $GITHUB_PATH + - name: Build + env: + IDF_PATH: ${{ github.workspace }}/esp-idf + run: source tools/ci.sh && ci_esp32_idf4_build diff --git a/.github/workflows/ports_esp8266.yml b/.github/workflows/ports_esp8266.yml new file mode 100644 index 0000000000000..f6f2fcaf4a80e --- /dev/null +++ b/.github/workflows/ports_esp8266.yml @@ -0,0 +1,23 @@ +name: esp8266 port + +on: + push: + pull_request: + paths: + - '.github/workflows/*.yml' + - 'tools/**' + - 'py/**' + - 'extmod/**' + - 'lib/**' + - 'drivers/**' + - 'ports/esp8266/**' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install packages + run: source tools/ci.sh && ci_esp8266_setup >> $GITHUB_PATH + - name: Build + run: source tools/ci.sh && ci_esp8266_build diff --git a/.github/workflows/ports_nrf.yml b/.github/workflows/ports_nrf.yml new file mode 100644 index 0000000000000..1ba3b0ce61e96 --- /dev/null +++ b/.github/workflows/ports_nrf.yml @@ -0,0 +1,23 @@ +name: nrf port + +on: + push: + pull_request: + paths: + - '.github/workflows/*.yml' + - 'tools/**' + - 'py/**' + - 'extmod/**' + - 'lib/**' + - 'drivers/**' + - 'ports/nrf/**' + +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Install packages + run: source tools/ci.sh && ci_nrf_setup + - name: Build + run: source tools/ci.sh && ci_nrf_build diff --git a/.github/workflows/ports_powerpc.yml b/.github/workflows/ports_powerpc.yml new file mode 100644 index 0000000000000..88fa59767b756 --- /dev/null +++ b/.github/workflows/ports_powerpc.yml @@ -0,0 +1,23 @@ +name: powerpc port + +on: + push: + pull_request: + paths: + - '.github/workflows/*.yml' + - 'tools/**' + - 'py/**' + - 'extmod/**' + - 'lib/**' + - 'drivers/**' + - 'ports/powerpc/**' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install packages + run: source tools/ci.sh && ci_powerpc_setup + - name: Build + run: source tools/ci.sh && ci_powerpc_build diff --git a/.github/workflows/ports_qemu-arm.yml b/.github/workflows/ports_qemu-arm.yml new file mode 100644 index 0000000000000..f456dfd74aff6 --- /dev/null +++ b/.github/workflows/ports_qemu-arm.yml @@ -0,0 +1,26 @@ +name: qemu-arm port + +on: + push: + pull_request: + paths: + - '.github/workflows/*.yml' + - 'tools/**' + - 'py/**' + - 'extmod/**' + - 'lib/**' + - 'drivers/**' + - 'ports/qemu-arm/**' + +jobs: + build_and_test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install packages + run: source tools/ci.sh && ci_qemu_arm_setup + - name: Build + run: source tools/ci.sh && ci_qemu_arm_build + - name: Print failures + if: failure() + run: grep --text "FAIL" ports/qemu-arm/build/console.out diff --git a/.github/workflows/ports_samd.yml b/.github/workflows/ports_samd.yml new file mode 100644 index 0000000000000..bde4ed965faa0 --- /dev/null +++ b/.github/workflows/ports_samd.yml @@ -0,0 +1,23 @@ +name: samd port + +on: + push: + pull_request: + paths: + - '.github/workflows/*.yml' + - 'tools/**' + - 'py/**' + - 'extmod/**' + - 'lib/**' + - 'drivers/**' + - 'ports/samd/**' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install packages + run: source tools/ci.sh && ci_samd_setup + - name: Build + run: source tools/ci.sh && ci_samd_build diff --git a/.github/workflows/ports_stm32.yml b/.github/workflows/ports_stm32.yml new file mode 100644 index 0000000000000..4634339c96a6a --- /dev/null +++ b/.github/workflows/ports_stm32.yml @@ -0,0 +1,32 @@ +name: stm32 port + +on: + push: + pull_request: + paths: + - '.github/workflows/*.yml' + - 'tools/**' + - 'py/**' + - 'extmod/**' + - 'lib/**' + - 'drivers/**' + - 'ports/stm32/**' + +jobs: + build_pyb: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Install packages + run: source tools/ci.sh && ci_stm32_setup + - name: Build + run: source tools/ci.sh && ci_stm32_pyb_build + + build_nucleo: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Install packages + run: source tools/ci.sh && ci_stm32_setup + - name: Build + run: source tools/ci.sh && ci_stm32_nucleo_build diff --git a/.github/workflows/ports_teensy.yml b/.github/workflows/ports_teensy.yml new file mode 100644 index 0000000000000..7ae77c80af604 --- /dev/null +++ b/.github/workflows/ports_teensy.yml @@ -0,0 +1,23 @@ +name: teensy port + +on: + push: + pull_request: + paths: + - '.github/workflows/*.yml' + - 'tools/**' + - 'py/**' + - 'extmod/**' + - 'lib/**' + - 'drivers/**' + - 'ports/teensy/**' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install packages + run: source tools/ci.sh && ci_teensy_setup + - name: Build + run: source tools/ci.sh && ci_teensy_build diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml new file mode 100644 index 0000000000000..385b2c008a7c8 --- /dev/null +++ b/.github/workflows/ports_unix.yml @@ -0,0 +1,176 @@ +name: unix port + +on: + push: + pull_request: + paths: + - '.github/workflows/*.yml' + - 'tools/**' + - 'py/**' + - 'extmod/**' + - 'lib/**' + - 'examples/**' + - 'ports/unix/**' + +jobs: + minimal: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build + run: source tools/ci.sh && ci_unix_minimal_build + - name: Run main test suite + run: source tools/ci.sh && ci_unix_minimal_run_tests + - name: Print failures + if: failure() + run: tests/run-tests --print-failures + + standard: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build + run: source tools/ci.sh && ci_unix_standard_build + - name: Run main test suite + run: source tools/ci.sh && ci_unix_standard_run_tests + - name: Run performance benchmarks + run: source tools/ci.sh && ci_unix_standard_run_perfbench + - name: Print failures + if: failure() + run: tests/run-tests --print-failures + + coverage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install packages + run: source tools/ci.sh && ci_unix_coverage_setup + - name: Build + run: source tools/ci.sh && ci_unix_coverage_build + - name: Run main test suite + run: source tools/ci.sh && ci_unix_coverage_run_tests + - name: Build native mpy modules + run: source tools/ci.sh && ci_native_mpy_modules_build + - name: Test importing .mpy generated by mpy_ld.py + run: source tools/ci.sh && ci_unix_coverage_run_native_mpy_tests + - name: Run lcov coverage analysis + run: | + mkdir -p coverage + lcov --rc lcov_branch_coverage=1 --directory ports/unix/build-coverage --capture --output-file coverage/lcov.info.all + lcov --remove coverage/lcov.info.all '*/lib/*' '*/ports/unix/*' '*/utils/*' --output-file coverage/lcov.info + - name: Send to coveralls + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Print failures + if: failure() + run: tests/run-tests --print-failures + + coverage_32bit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install packages + run: source tools/ci.sh && ci_unix_32bit_setup + - name: Build + run: source tools/ci.sh && ci_unix_coverage_32bit_build + - name: Run main test suite + run: source tools/ci.sh && ci_unix_coverage_32bit_run_tests + - name: Build native mpy modules + run: source tools/ci.sh && ci_native_mpy_modules_32bit_build + - name: Test importing .mpy generated by mpy_ld.py + run: source tools/ci.sh && ci_unix_coverage_32bit_run_native_mpy_tests + - name: Print failures + if: failure() + run: tests/run-tests --print-failures + + nanbox: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install packages + run: source tools/ci.sh && ci_unix_32bit_setup + - name: Build + run: source tools/ci.sh && ci_unix_nanbox_build + - name: Run main test suite + run: source tools/ci.sh && ci_unix_nanbox_run_tests + - name: Print failures + if: failure() + run: tests/run-tests --print-failures + + float: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build + run: source tools/ci.sh && ci_unix_float_build + - name: Run main test suite + run: source tools/ci.sh && ci_unix_float_run_tests + - name: Print failures + if: failure() + run: tests/run-tests --print-failures + + stackless_clang: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Install packages + run: source tools/ci.sh && ci_unix_clang_setup + - name: Build + run: source tools/ci.sh && ci_unix_stackless_clang_build + - name: Run main test suite + run: source tools/ci.sh && ci_unix_stackless_clang_run_tests + - name: Print failures + if: failure() + run: tests/run-tests --print-failures + + float_clang: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Install packages + run: source tools/ci.sh && ci_unix_clang_setup + - name: Build + run: source tools/ci.sh && ci_unix_float_clang_build + - name: Run main test suite + run: source tools/ci.sh && ci_unix_float_clang_run_tests + - name: Print failures + if: failure() + run: tests/run-tests --print-failures + + settrace: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build + run: source tools/ci.sh && ci_unix_settrace_build + - name: Run main test suite + run: source tools/ci.sh && ci_unix_settrace_run_tests + - name: Print failures + if: failure() + run: tests/run-tests --print-failures + + settrace_stackless: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build + run: source tools/ci.sh && ci_unix_settrace_stackless_build + - name: Run main test suite + run: source tools/ci.sh && ci_unix_settrace_stackless_run_tests + - name: Print failures + if: failure() + run: tests/run-tests --print-failures + + macos: + runs-on: macos-11.0 + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v1 + - name: Build + run: source tools/ci.sh && ci_unix_macos_build + - name: Run tests + run: source tools/ci.sh && ci_unix_macos_run_tests + - name: Print failures + if: failure() + run: tests/run-tests --print-failures diff --git a/.github/workflows/ports_windows.yml b/.github/workflows/ports_windows.yml new file mode 100644 index 0000000000000..1bfe40c7fded2 --- /dev/null +++ b/.github/workflows/ports_windows.yml @@ -0,0 +1,23 @@ +name: windows port + +on: + push: + pull_request: + paths: + - '.github/workflows/*.yml' + - 'tools/**' + - 'py/**' + - 'extmod/**' + - 'lib/**' + - 'ports/unix/**' + - 'ports/windows/**' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install packages + run: source tools/ci.sh && ci_windows_setup + - name: Build + run: source tools/ci.sh && ci_windows_build diff --git a/.github/workflows/ports_zephyr.yml b/.github/workflows/ports_zephyr.yml new file mode 100644 index 0000000000000..d9ae2b8c55a2a --- /dev/null +++ b/.github/workflows/ports_zephyr.yml @@ -0,0 +1,24 @@ +name: zephyr port + +on: + push: + pull_request: + paths: + - '.github/workflows/ports_zephyr.yml' + - 'tools/**' + - 'py/**' + - 'extmod/**' + - 'lib/**' + - 'ports/zephyr/**' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install packages + run: source tools/ci.sh && ci_zephyr_setup + - name: Install Zephyr + run: source tools/ci.sh && ci_zephyr_install + - name: Build + run: source tools/ci.sh && ci_zephyr_build From ee3706f4bdcb06c60eef16eff38dd388509021d8 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 29 Nov 2020 18:06:57 +1100 Subject: [PATCH 095/179] travis: Stop using Travis for CI. Travis now limits the amount of free minutes for open-source projects, and it does not provide enough for this project. So stop using it and instead use on GitHub Actions. Signed-off-by: Damien George --- .travis.yml | 396 ---------------------------------------------------- 1 file changed, 396 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 14595ddeb1d39..0000000000000 --- a/.travis.yml +++ /dev/null @@ -1,396 +0,0 @@ -# global options -dist: xenial -language: - - c -compiler: - - gcc -cache: - directories: - - "${HOME}/persist" -env: - global: - - MAKEOPTS="-j4" -git: - submodules: false - -# define the successive stages -stages: - - name: test - -# define the jobs for the stages -# approx order of the jobs has longest running first to optimise total time -jobs: - include: - # check code formatting - - stage: test - os: linux - dist: bionic - name: "code formatting" - before_install: - - sudo apt-add-repository --yes --update ppa:pybricks/ppa - install: - - sudo apt-get install uncrustify python3-pip - - uncrustify --version - - pip3 install --user setuptools - - pip3 install --user black - - black --version - script: - - tools/codeformat.py - - git diff --exit-code - - # zephyr port - - stage: test - name: "zephyr port build" - services: - - docker - before_install: - - docker pull zephyrprojectrtos/ci:v0.11.8 - - > - docker run --name zephyr-ci -d -it - -v "$(pwd)":/micropython - -e ZEPHYR_SDK_INSTALL_DIR=/opt/sdk/zephyr-sdk-0.11.3 - -e ZEPHYR_TOOLCHAIN_VARIANT=zephyr - -w /micropython/ports/zephyr - zephyrprojectrtos/ci:v0.11.8 - - docker ps -a - install: - - docker exec zephyr-ci west init --mr v2.4.0 /zephyrproject - - docker exec -w /zephyrproject zephyr-ci west update - - docker exec -w /zephyrproject zephyr-ci west zephyr-export - script: - - docker exec zephyr-ci bash -c "make clean; ./make-minimal ${MAKEOPTS}" - - docker exec zephyr-ci bash -c "make clean; ./make-minimal ${MAKEOPTS} BOARD=frdm_k64f" - - docker exec zephyr-ci bash -c "make clean; make ${MAKEOPTS}" - - docker exec zephyr-ci bash -c "make clean; make ${MAKEOPTS} BOARD=frdm_k64f" - - docker exec zephyr-ci bash -c "make clean; make ${MAKEOPTS} BOARD=mimxrt1050_evk" - - docker exec zephyr-ci bash -c "make clean; make ${MAKEOPTS} BOARD=reel_board" - - # unix port on OSX (first in list because the build VM takes a long time to start) - - stage: test - os: osx - osx_image: xcode11.3 - name: "unix port build with clang on OSX" - env: - - PKG_CONFIG_PATH=/usr/local/opt/libffi/lib/pkgconfig - script: - - make ${MAKEOPTS} -C mpy-cross - - make ${MAKEOPTS} -C ports/unix submodules - - make ${MAKEOPTS} -C ports/unix deplibs - - make ${MAKEOPTS} -C ports/unix - # OSX has poor time resolution and the following tests do not have the correct output - - (cd tests && ./run-tests --exclude 'uasyncio_(basic|heaplock|lock|wait_task)') - # check for additional compiler errors/warnings - - make ${MAKEOPTS} -C ports/unix VARIANT=coverage submodules - - make ${MAKEOPTS} -C ports/unix VARIANT=coverage - after_failure: - - tests/run-tests --print-failures - - # stm32 port - - stage: test - name: "stm32 port build" - install: - # need newer gcc version for Cortex-M7 support - - sudo add-apt-repository -y ppa:team-gcc-arm-embedded/ppa - - sudo apt-get update -qq || true - - sudo apt-get install gcc-arm-embedded libnewlib-arm-none-eabi - - arm-none-eabi-gcc --version - script: - - make ${MAKEOPTS} -C mpy-cross - - make ${MAKEOPTS} -C ports/stm32 submodules - - git submodule update --init lib/btstack - - make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_F091RC - - make ${MAKEOPTS} -C ports/stm32 BOARD=PYBV11 MICROPY_PY_WIZNET5K=5200 MICROPY_PY_CC3K=1 USER_C_MODULES=../../examples/usercmodule CFLAGS_EXTRA="-DMODULE_CEXAMPLE_ENABLED=1 -DMODULE_CPPEXAMPLE_ENABLED=1" - - make ${MAKEOPTS} -C ports/stm32 BOARD=PYBD_SF2 - - make ${MAKEOPTS} -C ports/stm32 BOARD=PYBD_SF6 NANBOX=1 MICROPY_BLUETOOTH_NIMBLE=0 MICROPY_BLUETOOTH_BTSTACK=1 - - make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_H743ZI CFLAGS_EXTRA='-DMICROPY_PY_THREAD=1' - - make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_L073RZ - - make ${MAKEOPTS} -C ports/stm32 BOARD=STM32L476DISC - - make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_WB55 - - make ${MAKEOPTS} -C ports/stm32/mboot BOARD=PYBV10 CFLAGS_EXTRA='-DMBOOT_FSLOAD=1 -DMBOOT_VFS_LFS2=1' - - make ${MAKEOPTS} -C ports/stm32/mboot BOARD=PYBD_SF6 - - make ${MAKEOPTS} -C ports/stm32/mboot BOARD=NUCLEO_WB55 - - # qemu-arm port - - stage: test - dist: bionic # needed for more recent version of qemu-system-arm with mps2-an385 target - name: "qemu-arm port build and tests" - install: - - sudo apt-get install gcc-arm-none-eabi libnewlib-arm-none-eabi qemu-system - - arm-none-eabi-gcc --version - - qemu-system-arm --version - script: - - make ${MAKEOPTS} -C mpy-cross - - make ${MAKEOPTS} -C ports/qemu-arm CFLAGS_EXTRA=-DMP_ENDIANNESS_BIG=1 - - make ${MAKEOPTS} -C ports/qemu-arm clean - - make ${MAKEOPTS} -C ports/qemu-arm -f Makefile.test test - after_failure: - - grep --text "FAIL" ports/qemu-arm/build/console.out - - # unix coverage - - stage: test - name: "unix coverage build and tests" - install: - - sudo apt-get install python3-pip - - sudo pip install cpp-coveralls - - sudo pip3 install setuptools - - sudo pip3 install pyelftools - - gcc --version - - python3 --version - script: - - make ${MAKEOPTS} -C mpy-cross - - make ${MAKEOPTS} -C ports/unix VARIANT=coverage submodules - - make ${MAKEOPTS} -C ports/unix VARIANT=coverage deplibs - - make ${MAKEOPTS} -C ports/unix VARIANT=coverage - # run the main test suite - - make -C ports/unix VARIANT=coverage test_full - - (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=../ports/unix/micropython-coverage ./run-multitests.py multi_net/*.py) || travis_terminate 1 - # test building native mpy modules - - make -C examples/natmod/features1 ARCH=x64 - - make -C examples/natmod/features2 ARCH=x64 - - make -C examples/natmod/btree ARCH=x64 - - make -C examples/natmod/framebuf ARCH=x64 - - make -C examples/natmod/uheapq ARCH=x64 - - make -C examples/natmod/urandom ARCH=x64 - - make -C examples/natmod/ure ARCH=x64 - - make -C examples/natmod/uzlib ARCH=x64 - # test importing .mpy generated by mpy_ld.py - - MICROPYPATH=examples/natmod/features2 ./ports/unix/micropython-coverage -m features2 - - (cd tests && ./run-natmodtests.py extmod/{btree*,framebuf*,uheapq*,ure*,uzlib*}.py) - # run coveralls coverage analysis (try to, even if some builds/tests failed) - - (cd ports/unix && coveralls --root ../.. --build-root . --gcov $(which gcov) --gcov-options '\-o build-coverage/' --include py --include extmod) - after_failure: - - tests/run-tests --print-failures - - # unix coverage 32-bit - - stage: test - name: "unix coverage 32-bit build and tests" - install: - - sudo apt-get install gcc-multilib g++-multilib libffi-dev:i386 - - sudo apt-get install python3-pip - - sudo pip3 install setuptools - - sudo pip3 install pyelftools - - gcc --version - - python3 --version - script: - - make ${MAKEOPTS} -C mpy-cross - - make ${MAKEOPTS} -C ports/unix MICROPY_FORCE_32BIT=1 VARIANT=coverage submodules - - make ${MAKEOPTS} -C ports/unix MICROPY_FORCE_32BIT=1 VARIANT=coverage deplibs - - make ${MAKEOPTS} -C ports/unix MICROPY_FORCE_32BIT=1 VARIANT=coverage - # run the main test suite - - make -C ports/unix MICROPY_FORCE_32BIT=1 VARIANT=coverage test_full || travis_terminate 1 - # test building native mpy modules - - make -C examples/natmod/features1 ARCH=x86 - - make -C examples/natmod/features2 ARCH=x86 - - make -C examples/natmod/btree ARCH=x86 - - make -C examples/natmod/framebuf ARCH=x86 - - make -C examples/natmod/uheapq ARCH=x86 - - make -C examples/natmod/urandom ARCH=x86 - - make -C examples/natmod/ure ARCH=x86 - - make -C examples/natmod/uzlib ARCH=x86 - # test importing .mpy generated by mpy_ld.py - - MICROPYPATH=examples/natmod/features2 ./ports/unix/micropython-coverage -m features2 - - (cd tests && ./run-natmodtests.py --arch x86 extmod/{btree*,framebuf*,uheapq*,ure*,uzlib*}.py) - after_failure: - - tests/run-tests --print-failures - - # standard unix port - - stage: test - name: "unix port build and tests" - script: - - make ${MAKEOPTS} -C mpy-cross - - make ${MAKEOPTS} -C ports/unix submodules - - make ${MAKEOPTS} -C ports/unix deplibs - - make ${MAKEOPTS} -C ports/unix - - make ${MAKEOPTS} -C ports/unix test - - (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=../ports/unix/micropython ./run-perfbench.py 1000 1000) - after_failure: - - tests/run-tests --print-failures - - # unix nanbox/float (and using Python 2 to check it can run the build scripts) - - stage: test - name: "unix nanbox/float port build and tests" - install: - - sudo apt-get install gcc-multilib libffi-dev:i386 - script: - - make ${MAKEOPTS} -C mpy-cross PYTHON=python2 - - make ${MAKEOPTS} -C ports/unix VARIANT=nanbox submodules - - make ${MAKEOPTS} -C ports/unix PYTHON=python2 VARIANT=nanbox deplibs - - make ${MAKEOPTS} -C ports/unix PYTHON=python2 VARIANT=nanbox - - make ${MAKEOPTS} -C ports/unix PYTHON=python2 VARIANT=nanbox test_full || travis_terminate 1 - - make ${MAKEOPTS} -C ports/unix clean - - make ${MAKEOPTS} -C ports/unix CFLAGS_EXTRA="-DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_FLOAT" - - make ${MAKEOPTS} -C ports/unix test - after_failure: - - tests/run-tests --print-failures - - # unix stackless/float with clang - - stage: test - name: "unix stackless/float port build and tests with clang" - install: - - sudo apt-get install clang - script: - - make ${MAKEOPTS} -C mpy-cross CC=clang - - make ${MAKEOPTS} -C ports/unix submodules - - make ${MAKEOPTS} -C ports/unix CC=clang CFLAGS_EXTRA="-DMICROPY_STACKLESS=1 -DMICROPY_STACKLESS_STRICT=1" - - make ${MAKEOPTS} -C ports/unix CC=clang test || travis_terminate 1 - - make ${MAKEOPTS} -C ports/unix clean - - make ${MAKEOPTS} -C ports/unix CC=clang CFLAGS_EXTRA="-DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_FLOAT" - - make ${MAKEOPTS} -C ports/unix CC=clang test - after_failure: - - tests/run-tests --print-failures - - # unix with sys.settrace - - stage: test - name: "unix port with sys.settrace build and tests" - script: - - make ${MAKEOPTS} -C mpy-cross - - make ${MAKEOPTS} -C ports/unix MICROPY_PY_BTREE=0 MICROPY_PY_FFI=0 MICROPY_PY_USSL=0 CFLAGS_EXTRA="-DMICROPY_PY_SYS_SETTRACE=1" test || travis_terminate 1 - - make ${MAKEOPTS} -C ports/unix clean - - make ${MAKEOPTS} -C ports/unix MICROPY_PY_BTREE=0 MICROPY_PY_FFI=0 MICROPY_PY_USSL=0 CFLAGS_EXTRA="-DMICROPY_STACKLESS=1 -DMICROPY_STACKLESS_STRICT=1 -DMICROPY_PY_SYS_SETTRACE=1" test - after_failure: - - tests/run-tests --print-failures - - # minimal unix port with tests - - stage: test - name: "minimal unix port build and tests" - script: - - make ${MAKEOPTS} -C ports/unix VARIANT=minimal - - (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=../ports/unix/micropython-minimal ./run-tests -e exception_chain -e self_type_check -e subclass_native_init -d basics) - after_failure: - - tests/run-tests --print-failures - - # windows port via mingw - - stage: test - name: "windows port build via mingw" - install: - - sudo apt-get install gcc-mingw-w64 - script: - - make ${MAKEOPTS} -C mpy-cross - - make ${MAKEOPTS} -C ports/windows CROSS_COMPILE=i686-w64-mingw32- - - # esp32 w/ESP-IDFv3 port - - stage: test - name: "esp32 ESP-IDFv3 port build" - install: - - sudo apt-get install python3-pip - - sudo pip3 install 'pyparsing<2.4' - - curl -L https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz | tar zxf - - - export PATH=$(pwd)/xtensa-esp32-elf/bin:$PATH - - git clone https://github.com/espressif/esp-idf.git - - export IDF_PATH=$(pwd)/esp-idf - script: - - make ${MAKEOPTS} -C mpy-cross - - git -C esp-idf checkout $(grep "ESPIDF_SUPHASH_V3 :=" ports/esp32/Makefile | cut -d " " -f 3) - - git -C esp-idf submodule update --init components/json/cJSON components/esp32/lib components/esptool_py/esptool components/expat/expat components/lwip/lwip components/mbedtls/mbedtls components/micro-ecc/micro-ecc components/nghttp/nghttp2 components/nimble components/bt - - make ${MAKEOPTS} -C ports/esp32 submodules - - make ${MAKEOPTS} -C ports/esp32 - - # esp32 w/ESP-IDFv4 port - - stage: test - name: "esp32 ESP-IDFv4 port build" - install: - - sudo apt-get install python3-pip - - sudo pip3 install 'pyparsing<2.4' - - curl -L https://dl.espressif.com/dl/xtensa-esp32-elf-gcc8_2_0-esp-2019r2-linux-amd64.tar.gz | tar zxf - - - export PATH=$(pwd)/xtensa-esp32-elf/bin:$PATH - - git clone https://github.com/espressif/esp-idf.git - - export IDF_PATH=$(pwd)/esp-idf - script: - - make ${MAKEOPTS} -C mpy-cross - - git -C esp-idf checkout $(grep "ESPIDF_SUPHASH_V4 :=" ports/esp32/Makefile | cut -d " " -f 3) - - git -C esp-idf submodule update --init components/bt/controller/lib components/bt/host/nimble/nimble components/esp_wifi/lib_esp32 components/esptool_py/esptool components/lwip/lwip components/mbedtls/mbedtls - - make ${MAKEOPTS} -C ports/esp32 submodules - - make ${MAKEOPTS} -C ports/esp32 - - # esp8266 port - - stage: test - name: "esp8266 port build" - install: - - wget https://github.com/jepler/esp-open-sdk/releases/download/2018-06-10/xtensa-lx106-elf-standalone.tar.gz - - zcat xtensa-lx106-elf-standalone.tar.gz | tar x - - export PATH=$(pwd)/xtensa-lx106-elf/bin:$PATH - script: - - make ${MAKEOPTS} -C mpy-cross - - make ${MAKEOPTS} -C ports/esp8266 submodules - - make ${MAKEOPTS} -C ports/esp8266 - - make ${MAKEOPTS} -C ports/esp8266 BOARD=GENERIC_512K - - make ${MAKEOPTS} -C ports/esp8266 BOARD=GENERIC_1M - - # nrf port - - stage: test - name: "nrf port build" - install: - # need newer gcc version for Cortex-M33 support - - sudo add-apt-repository -y ppa:team-gcc-arm-embedded/ppa - - sudo apt-get update -qq || true - - sudo apt-get install gcc-arm-embedded - - sudo apt-get install libnewlib-arm-none-eabi - - arm-none-eabi-gcc --version - script: - - ports/nrf/drivers/bluetooth/download_ble_stack.sh s140_nrf52_6_1_1 - - make ${MAKEOPTS} -C ports/nrf submodules - - make ${MAKEOPTS} -C ports/nrf BOARD=pca10040 - - make ${MAKEOPTS} -C ports/nrf BOARD=microbit - - make ${MAKEOPTS} -C ports/nrf BOARD=pca10056 SD=s140 - - make ${MAKEOPTS} -C ports/nrf BOARD=pca10090 - - # bare-arm and minimal ports, with size-diff check - - stage: test - name: "bare-arm and minimal ports build and size-diff check" - install: - - sudo apt-get install gcc-multilib libffi-dev:i386 gcc-arm-none-eabi libnewlib-arm-none-eabi - - gcc --version - - arm-none-eabi-gcc --version - script: - # starts off at either the ref/pull/N/merge FETCH_HEAD, or the current branch HEAD - - git checkout -b pull_request # save the current location - - git remote add upstream https://github.com/micropython/micropython.git - - git fetch --depth=100 upstream - # build reference, save to size0 - # ignore any errors with this build, in case master is failing - - git checkout `git merge-base --fork-point upstream/master pull_request` - - git show -s - - tools/metrics.py clean bm - - tools/metrics.py build bm | tee ~/size0 || true - # build PR/branch, save to size1 - - git checkout pull_request - - git log upstream/master..HEAD - - tools/metrics.py clean bm - - tools/metrics.py build bm | tee ~/size1 || travis_terminate 1 - # compute diff of the code sizes - - tools/metrics.py diff --error-threshold 0 ~/size0 ~/size1 - - # cc3200 port - - stage: test - name: "cc3200 port build" - install: - - sudo apt-get install gcc-arm-none-eabi libnewlib-arm-none-eabi - script: - - make ${MAKEOPTS} -C ports/cc3200 BTARGET=application BTYPE=release - - make ${MAKEOPTS} -C ports/cc3200 BTARGET=bootloader BTYPE=release - - # samd port - - stage: test - name: "samd port build" - install: - - sudo apt-get install gcc-arm-none-eabi libnewlib-arm-none-eabi - script: - - make ${MAKEOPTS} -C ports/samd submodules - - make ${MAKEOPTS} -C ports/samd - - # teensy port - - stage: test - name: "teensy port build" - install: - - sudo apt-get install gcc-arm-none-eabi libnewlib-arm-none-eabi - script: - - make ${MAKEOPTS} -C ports/teensy - - # powerpc port - - stage: test - name: "powerpc port build" - install: - - sudo apt-get install gcc-powerpc64le-linux-gnu libc6-dev-ppc64el-cross - script: - - make ${MAKEOPTS} -C ports/powerpc UART=potato - - make ${MAKEOPTS} -C ports/powerpc UART=lpc_serial From 02b44a0154aee3cfd2675b4ff180061b2b5bc40d Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 30 Nov 2020 10:28:48 +1100 Subject: [PATCH 096/179] tests/run-tests: Update skipped tests on CI for GitHub Actions. Signed-off-by: Damien George --- tests/run-tests | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/run-tests b/tests/run-tests index eebc8c4252700..23c30d3c3cb55 100755 --- a/tests/run-tests +++ b/tests/run-tests @@ -342,13 +342,9 @@ def run_tests(pyb, tests, args, result_dir): 'struct_endian', ) - # Some tests shouldn't be run under Travis CI - if os.getenv('TRAVIS') == 'true': - skip_tests.add('basics/memoryerror.py') - skip_tests.add('thread/thread_gc1.py') # has reliability issues - skip_tests.add('thread/thread_lock4.py') # has reliability issues - skip_tests.add('thread/stress_heap.py') # has reliability issues - skip_tests.add('thread/stress_recurse.py') # has reliability issues + # Some tests shouldn't be run on GitHub Actions + if os.getenv('GITHUB_ACTIONS') == 'true': + skip_tests.add('thread/stress_schedule.py') # has reliability issues if upy_float_precision == 0: skip_tests.add('extmod/uctypes_le_float.py') From 2f723d83c0b30a272d0ef41e8b7eda729de1e0e0 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 30 Nov 2020 10:29:20 +1100 Subject: [PATCH 097/179] README: Update badges for new GitHub Actions workflows. Signed-off-by: Damien George --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 57daebf87263b..f75590a5ba7d0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.com/micropython/micropython.png?branch=master)](https://travis-ci.com/micropython/micropython) [![Coverage Status](https://coveralls.io/repos/micropython/micropython/badge.png?branch=master)](https://coveralls.io/r/micropython/micropython?branch=master) +[![CI badge](https://github.com/micropython/micropython/workflows/unix%20port/badge.svg)](https://github.com/micropython/micropython/actions?query=branch%3Amaster+event%3Apush) [![Coverage badge](https://coveralls.io/repos/micropython/micropython/badge.png?branch=master)](https://coveralls.io/r/micropython/micropython?branch=master) The MicroPython project ======================= From f7225d1c9527c9839d80b8f73641ddb4ebbe6105 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 30 Nov 2020 10:33:46 +1100 Subject: [PATCH 098/179] github/workflows: Run unix and qemu-arm workflows when tests change. Signed-off-by: Damien George --- .github/workflows/ports_qemu-arm.yml | 3 ++- .github/workflows/ports_unix.yml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ports_qemu-arm.yml b/.github/workflows/ports_qemu-arm.yml index f456dfd74aff6..8d144ca3cb7aa 100644 --- a/.github/workflows/ports_qemu-arm.yml +++ b/.github/workflows/ports_qemu-arm.yml @@ -11,6 +11,7 @@ on: - 'lib/**' - 'drivers/**' - 'ports/qemu-arm/**' + - 'tests/**' jobs: build_and_test: @@ -19,7 +20,7 @@ jobs: - uses: actions/checkout@v2 - name: Install packages run: source tools/ci.sh && ci_qemu_arm_setup - - name: Build + - name: Build and run test suite run: source tools/ci.sh && ci_qemu_arm_build - name: Print failures if: failure() diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index 385b2c008a7c8..552fd72d8440e 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -11,6 +11,7 @@ on: - 'lib/**' - 'examples/**' - 'ports/unix/**' + - 'tests/**' jobs: minimal: From bb24c69b909e82cc1b6e82a28f14fa732f596b9f Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 6 Oct 2020 12:11:10 +1100 Subject: [PATCH 099/179] lib/utils/pyexec: Add stdin-reader on raw REPL with flow control. Background: the friendly/normal REPL is intended for human use whereas the raw REPL is for computer use/automation. Raw REPL is used for things like pyboard.py script_to_run.py. The normal REPL has built-in flow control because it echos back the characters. That's not so with raw REPL and flow control is just implemented by rate limiting the amount of data that goes in. Currently it's fixed at 256 byte chunks every 10ms. This is sometimes too fast for slow MCUs or systems with small stdin buffers. It's also too slow for a lot of higher-end MCUs, ie it could be a lot faster. This commit adds a new raw REPL mode which includes flow control: the device will echo back a character after a certain number of bytes are sent to the host, and the host can use this to regulate the data going out to the device. The amount of characters is controlled by the device and sent to the host before communication starts. This flow control allows getting the maximum speed out of a serial link, regardless of the link or the device at the other end. Also, this new raw REPL mode parses and compiles the incoming data as it comes in. It does this by creating a "stdin reader" object which is then passed to the lexer. The lexer requests bytes from this "stdin reader" which retrieves bytes from the host, and does flow control. What this means is that no memory is used to store the script (in the existing raw REPL mode the device needs a big buffer to read in the script before it can pass it on to the lexer/parser/compiler). The only memory needed on the device is enough to parse and compile. Finally, it would be possible to extend this new raw REPL to allow bytecode (.mpy files) to be sent as well as text mode scripts (but that's not done in this commit). Some results follow. The test was to send a large 33k script that contains mostly comments and then prints out the heap, run via pyboard.py large.py. On PYBD-SF6, prior to this PR: $ ./pyboard.py large.py stack: 524 out of 23552 GC: total: 392192, used: 34464, free: 357728 No. of 1-blocks: 12, 2-blocks: 2, max blk sz: 2075, max free sz: 22345 GC memory layout; from 2001a3f0: 00000: h=hhhh=======================================hhBShShh==h=======h 00400: =====hh=B........h==h=========================================== 00800: ================================================================ 00c00: ================================================================ 01000: ================================================================ 01400: ================================================================ 01800: ================================================================ 01c00: ================================================================ 02000: ================================================================ 02400: ================================================================ 02800: ================================================================ 02c00: ================================================================ 03000: ================================================================ 03400: ================================================================ 03800: ================================================================ 03c00: ================================================================ 04000: ================================================================ 04400: ================================================================ 04800: ================================================================ 04c00: ================================================================ 05000: ================================================================ 05400: ================================================================ 05800: ================================================================ 05c00: ================================================================ 06000: ================================================================ 06400: ================================================================ 06800: ================================================================ 06c00: ================================================================ 07000: ================================================================ 07400: ================================================================ 07800: ================================================================ 07c00: ================================================================ 08000: ================================================================ 08400: ===============================================.....h==......... (349 lines all free) (the big blob of used memory is the large script). Same but with this PR: $ ./pyboard.py large.py stack: 524 out of 23552 GC: total: 392192, used: 1296, free: 390896 No. of 1-blocks: 12, 2-blocks: 3, max blk sz: 40, max free sz: 24420 GC memory layout; from 2001a3f0: 00000: h=hhhh=======================================hhBShShh==h=======h 00400: =====hh=h=B......h==.....h==.................................... (381 lines all free) The only thing in RAM is the compiled script (and some other unrelated items). Time to download before this PR: 1438ms, data rate: 230,799 bits/sec. Time to download with this PR: 119ms, data rate: 2,788,991 bits/sec. So it's more than 10 times faster, and uses significantly less RAM. Results are similar on other boards. On an stm32 board that connects via UART only at 115200 baud, the data rate goes from 80kbit/sec to 113kbit/sec, so gets close to saturating the UART link without loss of data. The new raw REPL mode also supports a single ctrl-C to break out of this flow-control mode, so that a ctrl-C can always get back to a known state. It's also backwards compatible with the original raw REPL mode, which is still supported with the same sequence of commands. The new raw REPL mode is activated by ctrl-E, which gives an error on devices that do not support the new mode. Signed-off-by: Damien George --- lib/utils/pyexec.c | 118 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/lib/utils/pyexec.c b/lib/utils/pyexec.c index 2b86af3bbaadd..86321ac12909f 100644 --- a/lib/utils/pyexec.c +++ b/lib/utils/pyexec.c @@ -56,6 +56,7 @@ STATIC bool repl_display_debugging_info = 0; #define EXEC_FLAG_SOURCE_IS_RAW_CODE (8) #define EXEC_FLAG_SOURCE_IS_VSTR (16) #define EXEC_FLAG_SOURCE_IS_FILENAME (32) +#define EXEC_FLAG_SOURCE_IS_READER (64) // parses, compiles and executes the code in the lexer // frees the lexer before returning @@ -91,6 +92,8 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input if (exec_flags & EXEC_FLAG_SOURCE_IS_VSTR) { const vstr_t *vstr = source; lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, vstr->buf, vstr->len, 0); + } else if (exec_flags & EXEC_FLAG_SOURCE_IS_READER) { + lex = mp_lexer_new(MP_QSTR__lt_stdin_gt_, *(mp_reader_t *)source); } else if (exec_flags & EXEC_FLAG_SOURCE_IS_FILENAME) { lex = mp_lexer_new_from_file(source); } else { @@ -122,6 +125,12 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input // uncaught exception mp_hal_set_interrupt_char(-1); // disable interrupt mp_handle_pending(false); // clear any pending exceptions (and run any callbacks) + + if (exec_flags & EXEC_FLAG_SOURCE_IS_READER) { + const mp_reader_t *reader = source; + reader->close(reader->data); + } + // print EOF after normal output if (exec_flags & EXEC_FLAG_PRINT_EOF) { mp_hal_stdout_tx_strn("\x04", 1); @@ -170,6 +179,99 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input } #if MICROPY_ENABLE_COMPILER + +// This can be configured by a port (and even configured to a function to be +// computed dynamically) to indicate the maximum number of bytes that can be +// held in the stdin buffer. +#ifndef MICROPY_REPL_STDIN_BUFFER_MAX +#define MICROPY_REPL_STDIN_BUFFER_MAX (256) +#endif + +typedef struct _mp_reader_stdin_t { + bool eof; + uint16_t window_max; + uint16_t window_remain; +} mp_reader_stdin_t; + +STATIC mp_uint_t mp_reader_stdin_readbyte(void *data) { + mp_reader_stdin_t *reader = (mp_reader_stdin_t *)data; + + if (reader->eof) { + return MP_READER_EOF; + } + + int c = mp_hal_stdin_rx_chr(); + + if (c == CHAR_CTRL_C || c == CHAR_CTRL_D) { + reader->eof = true; + mp_hal_stdout_tx_strn("\x04", 1); // indicate end to host + if (c == CHAR_CTRL_C) { + #if MICROPY_KBD_EXCEPTION + MP_STATE_VM(mp_kbd_exception).traceback_data = NULL; + nlr_raise(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))); + #else + mp_raise_type(&mp_type_KeyboardInterrupt); + #endif + } else { + return MP_READER_EOF; + } + } + + if (--reader->window_remain == 0) { + mp_hal_stdout_tx_strn("\x01", 1); // indicate window available to host + reader->window_remain = reader->window_max; + } + + return c; +} + +STATIC void mp_reader_stdin_close(void *data) { + mp_reader_stdin_t *reader = (mp_reader_stdin_t *)data; + if (!reader->eof) { + reader->eof = true; + mp_hal_stdout_tx_strn("\x04", 1); // indicate end to host + for (;;) { + int c = mp_hal_stdin_rx_chr(); + if (c == CHAR_CTRL_C || c == CHAR_CTRL_D) { + break; + } + } + } +} + +STATIC void mp_reader_new_stdin(mp_reader_t *reader, mp_reader_stdin_t *reader_stdin, uint16_t buf_max) { + // Make flow-control window half the buffer size, and indicate to the host that 2x windows are + // free (sending the window size implicitly indicates that a window is free, and then the 0x01 + // indicates that another window is free). + size_t window = buf_max / 2; + char reply[3] = { window & 0xff, window >> 8, 0x01 }; + mp_hal_stdout_tx_strn(reply, sizeof(reply)); + + reader_stdin->eof = false; + reader_stdin->window_max = window; + reader_stdin->window_remain = window; + reader->data = reader_stdin; + reader->readbyte = mp_reader_stdin_readbyte; + reader->close = mp_reader_stdin_close; +} + +STATIC int do_reader_stdin(int c) { + if (c != 'A') { + // Unsupported command. + mp_hal_stdout_tx_strn("R\x00", 2); + return 0; + } + + // Indicate reception of command. + mp_hal_stdout_tx_strn("R\x01", 2); + + mp_reader_t reader; + mp_reader_stdin_t reader_stdin; + mp_reader_new_stdin(&reader, &reader_stdin, MICROPY_REPL_STDIN_BUFFER_MAX); + int exec_flags = EXEC_FLAG_PRINT_EOF | EXEC_FLAG_SOURCE_IS_READER; + return parse_compile_execute(&reader, MP_PARSE_FILE_INPUT, exec_flags); +} + #if MICROPY_REPL_EVENT_DRIVEN typedef struct _repl_t { @@ -203,6 +305,13 @@ void pyexec_event_repl_init(void) { STATIC int pyexec_raw_repl_process_char(int c) { if (c == CHAR_CTRL_A) { // reset raw REPL + if (vstr_len(MP_STATE_VM(repl_line)) == 2 && vstr_str(MP_STATE_VM(repl_line))[0] == CHAR_CTRL_E) { + int ret = do_reader_stdin(vstr_str(MP_STATE_VM(repl_line))[1]); + if (ret & PYEXEC_FORCED_EXIT) { + return ret; + } + goto reset; + } mp_hal_stdout_tx_str("raw REPL; CTRL-B to exit\r\n"); goto reset; } else if (c == CHAR_CTRL_B) { @@ -388,6 +497,15 @@ int pyexec_raw_repl(void) { int c = mp_hal_stdin_rx_chr(); if (c == CHAR_CTRL_A) { // reset raw REPL + if (vstr_len(&line) == 2 && vstr_str(&line)[0] == CHAR_CTRL_E) { + int ret = do_reader_stdin(vstr_str(&line)[1]); + if (ret & PYEXEC_FORCED_EXIT) { + return ret; + } + vstr_reset(&line); + mp_hal_stdout_tx_str(">"); + continue; + } goto raw_repl_reset; } else if (c == CHAR_CTRL_B) { // change to friendly REPL From a59282b9bfb6928cd68b696258c0dd2280244eb3 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 8 Oct 2020 23:58:02 +1100 Subject: [PATCH 100/179] tools/pyboard.py: Add fast raw-paste mode. This commit adds support to pyboard.py for the new raw REPL paste mode. Note that this new pyboard.py is fully backwards compatible with old devices (it detects if the device supports the new raw REPL paste mode). Signed-off-by: Damien George --- tools/pyboard.py | 57 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/tools/pyboard.py b/tools/pyboard.py index c97ddbe487be3..3ecdf03f156f7 100755 --- a/tools/pyboard.py +++ b/tools/pyboard.py @@ -253,6 +253,7 @@ def inWaiting(self): class Pyboard: def __init__(self, device, baudrate=115200, user="micro", password="python", wait=0): + self.use_raw_paste = True if device.startswith("exec:"): self.serial = ProcessToSerial(device[len("exec:") :]) elif device.startswith("execpty:"): @@ -359,6 +360,41 @@ def follow(self, timeout, data_consumer=None): # return normal and error output return data, data_err + def raw_paste_write(self, command_bytes): + # Read initial header, with window size. + data = self.serial.read(2) + window_size = data[0] | data[1] << 8 + window_remain = window_size + + # Write out the command_bytes data. + i = 0 + while i < len(command_bytes): + while window_remain == 0 or self.serial.inWaiting(): + data = self.serial.read(1) + if data == b"\x01": + # Device indicated that a new window of data can be sent. + window_remain += window_size + elif data == b"\x04": + # Device indicated abrupt end. Acknowledge it and finish. + self.serial.write(b"\x04") + return + else: + # Unexpected data from device. + raise PyboardError("unexpected read during raw paste: {}".format(data)) + # Send out as much data as possible that fits within the allowed window. + b = command_bytes[i : min(i + window_remain, len(command_bytes))] + self.serial.write(b) + window_remain -= len(b) + i += len(b) + + # Indicate end of data. + self.serial.write(b"\x04") + + # Wait for device to acknowledge end of data. + data = self.read_until(1, b"\x04") + if not data.endswith(b"\x04"): + raise PyboardError("could not complete raw paste: {}".format(data)) + def exec_raw_no_follow(self, command): if isinstance(command, bytes): command_bytes = command @@ -370,7 +406,26 @@ def exec_raw_no_follow(self, command): if not data.endswith(b">"): raise PyboardError("could not enter raw repl") - # write command + if self.use_raw_paste: + # Try to enter raw-paste mode. + self.serial.write(b"\x05A\x01") + data = self.serial.read(2) + if data == b"R\x00": + # Device understood raw-paste command but doesn't support it. + pass + elif data == b"R\x01": + # Device supports raw-paste mode, write out the command using this mode. + return self.raw_paste_write(command_bytes) + else: + # Device doesn't support raw-paste, fall back to normal raw REPL. + data = self.read_until(1, b"w REPL; CTRL-B to exit\r\n>") + if not data.endswith(b"w REPL; CTRL-B to exit\r\n>"): + print(data) + raise PyboardError("could not enter raw repl") + # Don't try to use raw-paste mode again for this connection. + self.use_raw_paste = False + + # Write command using standard raw REPL, 256 bytes every 10ms. for i in range(0, len(command_bytes), 256): self.serial.write(command_bytes[i : min(i + 256, len(command_bytes))]) time.sleep(0.01) From a14ca31e8579a07f263bca0dd4b0dd03f43befa2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 30 Nov 2020 11:34:23 +1100 Subject: [PATCH 101/179] docs/reference/repl.rst: Add information about new raw-paste mode. Signed-off-by: Damien George --- docs/reference/repl.rst | 100 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 6 deletions(-) diff --git a/docs/reference/repl.rst b/docs/reference/repl.rst index 06ba918117f33..55f76ee1a03b9 100644 --- a/docs/reference/repl.rst +++ b/docs/reference/repl.rst @@ -196,17 +196,105 @@ So you can use the underscore to save the result in a variable. For example: 15 >>> -Raw mode --------- +Raw mode and raw-paste mode +--------------------------- -Raw mode is not something that a person would normally use. It is intended for -programmatic use. It essentially behaves like paste mode with echo turned off. +Raw mode (also called raw REPL) is not something that a person would normally use. +It is intended for programmatic use and essentially behaves like paste mode with +echo turned off, and with optional flow control. Raw mode is entered using Ctrl-A. You then send your python code, followed by a Ctrl-D. The Ctrl-D will be acknowledged by 'OK' and then the python code will be compiled and executed. Any output (or errors) will be sent back. Entering Ctrl-B will leave raw mode and return the the regular (aka friendly) REPL. -The ``tools/pyboard.py`` program uses the raw REPL to execute python files on the -MicroPython board. +Raw-paste mode is an additional mode within the raw REPL that includes flow control, +and which compiles code as it receives it. This makes it more robust for high-speed +transfer of code into the device, and it also uses less RAM when receiving because +it does not need to store a verbatim copy of the code before compiling (unlike +standard raw mode). +Raw-paste mode uses the following protocol: + +#. Enter raw REPL as usual via ctrl-A. + +#. Write 3 bytes: ``b"\x05A\x01"`` (ie ctrl-E then "A" then ctrl-A). + +#. Read 2 bytes to determine if the device entered raw-paste mode: + + * If the result is ``b"R\x00"`` then the device understands the command but + doesn't support raw paste. + + * If the result is ``b"R\x01"`` then the device does support raw paste and + has entered this mode. + + * Otherwise the result should be ``b"ra"`` and the device doesn't support raw + paste and the string ``b"w REPL; CTRL-B to exit\r\n>"`` should be read and + discarded. + +#. If the device is in raw-paste mode then continue, otherwise fallback to + standard raw mode. + +#. Read 2 bytes, this is the flow control window-size-increment (in bytes) + stored as a 16-bit unsigned little endian integer. The initial value for the + remaining-window-size variable should be set to this number. + +#. Write out the code to the device: + + * While there are bytes to send, write up to the remaining-window-size worth + of bytes, and decrease the remaining-window-size by the number of bytes + written. + + * If the remaining-window-size is 0, or there is a byte waiting to read, read + 1 byte. If this byte is ``b"\x01"`` then increase the remaining-window-size + by the window-size-increment from step 5. If this byte is ``b"\x04"`` then + the device wants to end the data reception, and ``b"\x04"`` should be + written to the device and no more code sent after that. (Note: if there is + a byte waiting to be read from the device then it does not need to be read + and acted upon immediately, the device will continue to consume incoming + bytes as long as reamining-window-size is greater than 0.) + +#. When all code has been written to the device, write ``b"\x04"`` to indicate + end-of-data. + +#. Read from the device until ``b"\x04"`` is received. At this point the device + has received and compiled all of the code that was sent and is executing it. + +#. The device outputs any characters produced by the executing code. When (if) + the code finishes ``b"\x04"`` will be output, followed by any exception that + was uncaught, followed again by ``b"\x04"``. It then goes back to the + standard raw REPL and outputs ``b">"``. + +For example, starting at a new line at the normal (friendly) REPL, if you write:: + + b"\x01\x05A\x01print(123)\x04" + +Then the device will respond with something like:: + + b"\r\nraw REPL; CTRL-B to exit\r\n>R\x01\x80\x00\x01\x04123\r\n\x04\x04>" + +Broken down over time this looks like:: + + # Step 1: enter raw REPL + write: b"\x01" + read: b"\r\nraw REPL; CTRL-B to exit\r\n>" + + # Step 2-5: enter raw-paste mode + write: b"\x05A\x01" + read: b"R\x01\x80\x00\x01" + + # Step 6-8: write out code + write: b"print(123)\x04" + read: b"\x04" + + # Step 9: code executes and result is read + read: b"123\r\n\x04\x04>" + +In this case the flow control window-size-increment is 128 and there are two +windows worth of data immediately available at the start, one from the initial +window-size-increment value and one from the explicit ``b"\x01"`` value that +is sent. So this means up to 256 bytes can be written to begin with before +waiting or checking for more incoming flow-control characters. + +The ``tools/pyboard.py`` program uses the raw REPL, including raw-paste mode, to +execute Python code on a MicroPython-enabled board. From ca40eb0fdadd5a963b9a065999b092f029a598d5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 30 Nov 2020 17:38:19 +1100 Subject: [PATCH 102/179] extmod/uasyncio: Delay calling Loop.call_exception_handler by 1 loop. When a tasks raises an exception which is uncaught, and no other task await's on that task, then an error message is printed (or a user function called) via a call to Loop.call_exception_handler. In CPython this call is made when the Task object is freed (eg via reference counting) because it's at that point that it is known that the exception that was raised will never be handled. MicroPython does not have reference counting and the current behaviour is to deal with uncaught exceptions as early as possible, ie as soon as they terminate the task. But this can be undesirable because in certain cases a task can start and raise an exception immediately (before any await is executed in that task's coro) and before any other task gets a chance to await on it to catch the exception. This commit changes the behaviour so that tasks which end due to an uncaught exception are scheduled one more time for execution, and if they are not await'ed on by the next scheduling loop, then the exception handler is called (eg the exception is printed out). Signed-off-by: Damien George --- extmod/moduasyncio.c | 39 +++++++++++++++++-- extmod/uasyncio/core.py | 16 ++++---- extmod/uasyncio/funcs.py | 4 +- extmod/uasyncio/task.py | 19 +++++++-- .../extmod/uasyncio_set_exception_handler.py | 12 +++++- .../uasyncio_set_exception_handler.py.exp | 1 + 6 files changed, 73 insertions(+), 18 deletions(-) diff --git a/extmod/moduasyncio.c b/extmod/moduasyncio.c index 0b15c9e0790a5..e8822c069741b 100644 --- a/extmod/moduasyncio.c +++ b/extmod/moduasyncio.c @@ -146,6 +146,9 @@ STATIC const mp_obj_type_t task_queue_type = { /******************************************************************************/ // Task class +// For efficiency, the task object is stored to the coro entry when the task is done. +#define TASK_IS_DONE(task) ((task)->coro == MP_OBJ_FROM_PTR(task)) + // This is the core uasyncio context with cur_task, _task_queue and CancelledError. STATIC mp_obj_t uasyncio_context = MP_OBJ_NULL; @@ -167,7 +170,7 @@ STATIC mp_obj_t task_make_new(const mp_obj_type_t *type, size_t n_args, size_t n STATIC mp_obj_t task_cancel(mp_obj_t self_in) { mp_obj_task_t *self = MP_OBJ_TO_PTR(self_in); // Check if task is already finished. - if (self->coro == mp_const_none) { + if (TASK_IS_DONE(self)) { return mp_const_false; } // Can't cancel self (not supported yet). @@ -209,6 +212,24 @@ STATIC mp_obj_t task_cancel(mp_obj_t self_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(task_cancel_obj, task_cancel); +STATIC mp_obj_t task_throw(mp_obj_t self_in, mp_obj_t value_in) { + // This task raised an exception which was uncaught; handle that now. + mp_obj_task_t *self = MP_OBJ_TO_PTR(self_in); + // Set the data because it was cleared by the main scheduling loop. + self->data = value_in; + if (self->waiting == mp_const_none) { + // Nothing await'ed on the task so call the exception handler. + mp_obj_t _exc_context = mp_obj_dict_get(uasyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR__exc_context)); + mp_obj_dict_store(_exc_context, MP_OBJ_NEW_QSTR(MP_QSTR_exception), value_in); + mp_obj_dict_store(_exc_context, MP_OBJ_NEW_QSTR(MP_QSTR_future), self_in); + mp_obj_t Loop = mp_obj_dict_get(uasyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_Loop)); + mp_obj_t call_exception_handler = mp_load_attr(Loop, MP_QSTR_call_exception_handler); + mp_call_function_1(call_exception_handler, _exc_context); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(task_throw_obj, task_throw); + STATIC void task_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { mp_obj_task_t *self = MP_OBJ_TO_PTR(self_in); if (dest[0] == MP_OBJ_NULL) { @@ -218,12 +239,15 @@ STATIC void task_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { } else if (attr == MP_QSTR_data) { dest[0] = self->data; } else if (attr == MP_QSTR_waiting) { - if (self->waiting != mp_const_none) { + if (self->waiting != mp_const_none && self->waiting != mp_const_false) { dest[0] = self->waiting; } } else if (attr == MP_QSTR_cancel) { dest[0] = MP_OBJ_FROM_PTR(&task_cancel_obj); dest[1] = self_in; + } else if (attr == MP_QSTR_throw) { + dest[0] = MP_OBJ_FROM_PTR(&task_throw_obj); + dest[1] = self_in; } else if (attr == MP_QSTR_ph_key) { dest[0] = self->ph_key; } @@ -246,14 +270,21 @@ STATIC mp_obj_t task_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) { (void)iter_buf; mp_obj_task_t *self = MP_OBJ_TO_PTR(self_in); if (self->waiting == mp_const_none) { - self->waiting = task_queue_make_new(&task_queue_type, 0, 0, NULL); + // The is the first access of the "waiting" entry. + if (TASK_IS_DONE(self)) { + // Signal that the completed-task has been await'ed on. + self->waiting = mp_const_false; + } else { + // Lazily allocate the waiting queue. + self->waiting = task_queue_make_new(&task_queue_type, 0, 0, NULL); + } } return self_in; } STATIC mp_obj_t task_iternext(mp_obj_t self_in) { mp_obj_task_t *self = MP_OBJ_TO_PTR(self_in); - if (self->coro == mp_const_none) { + if (TASK_IS_DONE(self)) { // Task finished, raise return value to caller so it can continue. nlr_raise(self->data); } else { diff --git a/extmod/uasyncio/core.py b/extmod/uasyncio/core.py index 045b4cd139182..6a84b0982c41f 100644 --- a/extmod/uasyncio/core.py +++ b/extmod/uasyncio/core.py @@ -185,8 +185,6 @@ def run_until_complete(main_task=None): if isinstance(er, StopIteration): return er.value raise er - # Save return value of coro to pass up to caller - t.data = er # Schedule any other tasks waiting on the completion of this task waiting = False if hasattr(t, "waiting"): @@ -194,13 +192,15 @@ def run_until_complete(main_task=None): _task_queue.push_head(t.waiting.pop_head()) waiting = True t.waiting = None # Free waiting queue head - # Print out exception for detached tasks if not waiting and not isinstance(er, excs_stop): - _exc_context["exception"] = er - _exc_context["future"] = t - Loop.call_exception_handler(_exc_context) - # Indicate task is done - t.coro = None + # An exception ended this detached task, so queue it for later + # execution to handle the uncaught exception if no other task retrieves + # the exception in the meantime (this is handled by Task.throw). + _task_queue.push_head(t) + # Indicate task is done by setting coro to the task object itself + t.coro = t + # Save return value of coro to pass up to caller + t.data = er # Create a new task from a coroutine and run it until it finishes diff --git a/extmod/uasyncio/funcs.py b/extmod/uasyncio/funcs.py index 6e1305c94f418..d306752312c6e 100644 --- a/extmod/uasyncio/funcs.py +++ b/extmod/uasyncio/funcs.py @@ -21,9 +21,9 @@ def cancel(aw, timeout, sleep): pass finally: # Cancel the "cancel" task if it's still active (optimisation instead of cancel_task.cancel()) - if cancel_task.coro is not None: + if cancel_task.coro is not cancel_task: core._task_queue.remove(cancel_task) - if cancel_task.coro is None: + if cancel_task.coro is cancel_task: # Cancel task ran to completion, ie there was a timeout raise core.TimeoutError return ret diff --git a/extmod/uasyncio/task.py b/extmod/uasyncio/task.py index 1788cf0ed0486..2420ab7193159 100644 --- a/extmod/uasyncio/task.py +++ b/extmod/uasyncio/task.py @@ -130,13 +130,16 @@ def __init__(self, coro, globals=None): self.ph_rightmost_parent = None # Paring heap def __iter__(self): - if not hasattr(self, "waiting"): + if self.coro is self: + # Signal that the completed-task has been await'ed on. + self.waiting = None + elif not hasattr(self, "waiting"): # Lazily allocated head of linked list of Tasks waiting on completion of this task. self.waiting = TaskQueue() return self def __next__(self): - if not self.coro: + if self.coro is self: # Task finished, raise return value to caller so it can continue. raise self.data else: @@ -147,7 +150,7 @@ def __next__(self): def cancel(self): # Check if task is already finished. - if self.coro is None: + if self.coro is self: return False # Can't cancel self (not supported yet). if self is core.cur_task: @@ -166,3 +169,13 @@ def cancel(self): core._task_queue.push_head(self) self.data = core.CancelledError return True + + def throw(self, value): + # This task raised an exception which was uncaught; handle that now. + # Set the data because it was cleared by the main scheduling loop. + self.data = value + if not hasattr(self, "waiting"): + # Nothing await'ed on the task so call the exception handler. + core._exc_context["exception"] = value + core._exc_context["future"] = self + core.Loop.call_exception_handler(core._exc_context) diff --git a/tests/extmod/uasyncio_set_exception_handler.py b/tests/extmod/uasyncio_set_exception_handler.py index ad62a79b7b7a8..fe7b83eb4d81a 100644 --- a/tests/extmod/uasyncio_set_exception_handler.py +++ b/tests/extmod/uasyncio_set_exception_handler.py @@ -32,13 +32,23 @@ async def main(): # Create a task that raises and uses the custom exception handler asyncio.create_task(task(0)) print("sleep") - await asyncio.sleep(0) + for _ in range(2): + await asyncio.sleep(0) # Create 2 tasks to test order of printing exception asyncio.create_task(task(1)) asyncio.create_task(task(2)) print("sleep") + for _ in range(2): + await asyncio.sleep(0) + + # Create a task, let it run, then await it (no exception should be printed) + t = asyncio.create_task(task(3)) await asyncio.sleep(0) + try: + await t + except ValueError as er: + print(repr(er)) print("done") diff --git a/tests/extmod/uasyncio_set_exception_handler.py.exp b/tests/extmod/uasyncio_set_exception_handler.py.exp index 4744641e54c3c..fb4711469dc5b 100644 --- a/tests/extmod/uasyncio_set_exception_handler.py.exp +++ b/tests/extmod/uasyncio_set_exception_handler.py.exp @@ -5,4 +5,5 @@ custom_handler ValueError(0, 1) sleep custom_handler ValueError(1, 2) custom_handler ValueError(2, 3) +ValueError(3, 4) done From 309dfe39e07d3c3c8ba873ed09116cea9f6f52c0 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 30 Nov 2020 17:38:30 +1100 Subject: [PATCH 103/179] extmod/uasyncio: Add Task.done() method. This is added because task.coro==None is no longer the way to detect if a task is finished. Providing a (CPython compatible) function for this allows the implementation to be abstracted away. Signed-off-by: Damien George --- extmod/moduasyncio.c | 9 ++++ extmod/uasyncio/task.py | 3 ++ tests/extmod/uasyncio_task_done.py | 66 ++++++++++++++++++++++++++ tests/extmod/uasyncio_task_done.py.exp | 24 ++++++++++ 4 files changed, 102 insertions(+) create mode 100644 tests/extmod/uasyncio_task_done.py create mode 100644 tests/extmod/uasyncio_task_done.py.exp diff --git a/extmod/moduasyncio.c b/extmod/moduasyncio.c index e8822c069741b..fe0f748cac986 100644 --- a/extmod/moduasyncio.c +++ b/extmod/moduasyncio.c @@ -167,6 +167,12 @@ STATIC mp_obj_t task_make_new(const mp_obj_type_t *type, size_t n_args, size_t n return MP_OBJ_FROM_PTR(self); } +STATIC mp_obj_t task_done(mp_obj_t self_in) { + mp_obj_task_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bool(TASK_IS_DONE(self)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(task_done_obj, task_done); + STATIC mp_obj_t task_cancel(mp_obj_t self_in) { mp_obj_task_t *self = MP_OBJ_TO_PTR(self_in); // Check if task is already finished. @@ -242,6 +248,9 @@ STATIC void task_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { if (self->waiting != mp_const_none && self->waiting != mp_const_false) { dest[0] = self->waiting; } + } else if (attr == MP_QSTR_done) { + dest[0] = MP_OBJ_FROM_PTR(&task_done_obj); + dest[1] = self_in; } else if (attr == MP_QSTR_cancel) { dest[0] = MP_OBJ_FROM_PTR(&task_cancel_obj); dest[1] = self_in; diff --git a/extmod/uasyncio/task.py b/extmod/uasyncio/task.py index 2420ab7193159..68ddf496f08a2 100644 --- a/extmod/uasyncio/task.py +++ b/extmod/uasyncio/task.py @@ -148,6 +148,9 @@ def __next__(self): # Set calling task's data to this task that it waits on, to double-link it. core.cur_task.data = self + def done(self): + return self.coro is self + def cancel(self): # Check if task is already finished. if self.coro is self: diff --git a/tests/extmod/uasyncio_task_done.py b/tests/extmod/uasyncio_task_done.py new file mode 100644 index 0000000000000..2700da8c34168 --- /dev/null +++ b/tests/extmod/uasyncio_task_done.py @@ -0,0 +1,66 @@ +# Test the Task.done() method + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +async def task(t, exc=None): + print("task start") + if t >= 0: + await asyncio.sleep(t) + if exc: + raise exc + print("task done") + + +async def main(): + # Task that finishes immediately. + print("=" * 10) + t = asyncio.create_task(task(-1)) + print(t.done()) + await asyncio.sleep(0) + print(t.done()) + await t + print(t.done()) + + # Task that starts, runs and finishes. + print("=" * 10) + t = asyncio.create_task(task(0.01)) + print(t.done()) + await asyncio.sleep(0) + print(t.done()) + await t + print(t.done()) + + # Task that raises immediately. + print("=" * 10) + t = asyncio.create_task(task(-1, ValueError)) + print(t.done()) + await asyncio.sleep(0) + print(t.done()) + try: + await t + except ValueError as er: + print(repr(er)) + print(t.done()) + + # Task that raises after a delay. + print("=" * 10) + t = asyncio.create_task(task(0.01, ValueError)) + print(t.done()) + await asyncio.sleep(0) + print(t.done()) + try: + await t + except ValueError as er: + print(repr(er)) + print(t.done()) + + +asyncio.run(main()) diff --git a/tests/extmod/uasyncio_task_done.py.exp b/tests/extmod/uasyncio_task_done.py.exp new file mode 100644 index 0000000000000..ddda04c5ec43b --- /dev/null +++ b/tests/extmod/uasyncio_task_done.py.exp @@ -0,0 +1,24 @@ +========== +False +task start +task done +True +True +========== +False +task start +False +task done +True +========== +False +task start +True +ValueError() +True +========== +False +task start +False +ValueError() +True From b505971069333a373d9f0c12138b64dab83fed72 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 1 Dec 2020 14:22:16 +1100 Subject: [PATCH 104/179] extmod/uasyncio: Fix cancellation handling of wait_for. This commit switches the roles of the helper task from a cancellation task to a runner task, to get the correct semantics for cancellation of wait_for. Some uasyncio tests are now disabled for the native emitter due to issues with native code generation of generators and yield-from. Fixes #5797. Signed-off-by: Damien George --- extmod/uasyncio/funcs.py | 52 ++++++++++++++------ tests/extmod/uasyncio_wait_for.py | 55 +++++++++++++++++++++ tests/extmod/uasyncio_wait_for.py.exp | 20 ++++++++ tests/extmod/uasyncio_wait_for_fwd.py | 60 +++++++++++++++++++++++ tests/extmod/uasyncio_wait_for_fwd.py.exp | 26 ++++++++++ tests/run-tests | 3 ++ 6 files changed, 200 insertions(+), 16 deletions(-) create mode 100644 tests/extmod/uasyncio_wait_for_fwd.py create mode 100644 tests/extmod/uasyncio_wait_for_fwd.py.exp diff --git a/extmod/uasyncio/funcs.py b/extmod/uasyncio/funcs.py index d306752312c6e..93f4fd256cafe 100644 --- a/extmod/uasyncio/funcs.py +++ b/extmod/uasyncio/funcs.py @@ -9,24 +9,44 @@ async def wait_for(aw, timeout, sleep=core.sleep): if timeout is None: return await aw - def cancel(aw, timeout, sleep): - await sleep(timeout) - aw.cancel() + def runner(waiter, aw): + nonlocal status, result + try: + result = await aw + s = True + except BaseException as er: + s = er + if status is None: + # The waiter is still waiting, set status for it and cancel it. + status = s + waiter.cancel() + + # Run aw in a separate runner task that manages its exceptions. + status = None + result = None + runner_task = core.create_task(runner(core.cur_task, aw)) - cancel_task = core.create_task(cancel(aw, timeout, sleep)) try: - ret = await aw - except core.CancelledError: - # Ignore CancelledError from aw, it's probably due to timeout - pass - finally: - # Cancel the "cancel" task if it's still active (optimisation instead of cancel_task.cancel()) - if cancel_task.coro is not cancel_task: - core._task_queue.remove(cancel_task) - if cancel_task.coro is cancel_task: - # Cancel task ran to completion, ie there was a timeout - raise core.TimeoutError - return ret + # Wait for the timeout to elapse. + await sleep(timeout) + except core.CancelledError as er: + if status is True: + # aw completed successfully and cancelled the sleep, so return aw's result. + return result + elif status is None: + # This wait_for was cancelled externally, so cancel aw and re-raise. + status = True + runner_task.cancel() + raise er + else: + # aw raised an exception, propagate it out to the caller. + raise status + + # The sleep finished before aw, so cancel aw and raise TimeoutError. + status = True + runner_task.cancel() + await runner_task + raise core.TimeoutError def wait_for_ms(aw, timeout): diff --git a/tests/extmod/uasyncio_wait_for.py b/tests/extmod/uasyncio_wait_for.py index 92fd174b846db..9612d16204043 100644 --- a/tests/extmod/uasyncio_wait_for.py +++ b/tests/extmod/uasyncio_wait_for.py @@ -31,30 +31,85 @@ async def task_raise(): raise ValueError +async def task_cancel_other(t, other): + print("task_cancel_other start") + await asyncio.sleep(t) + print("task_cancel_other cancel") + other.cancel() + + +async def task_wait_for_cancel(id, t, t_wait): + print("task_wait_for_cancel start") + try: + await asyncio.wait_for(task(id, t), t_wait) + except asyncio.CancelledError as er: + print("task_wait_for_cancel cancelled") + raise er + + +async def task_wait_for_cancel_ignore(t_wait): + print("task_wait_for_cancel_ignore start") + try: + await asyncio.wait_for(task_catch(), t_wait) + except asyncio.CancelledError as er: + print("task_wait_for_cancel_ignore cancelled") + raise er + + async def main(): + sep = "-" * 10 + # When task finished before the timeout print(await asyncio.wait_for(task(1, 0.01), 10)) + print(sep) # When timeout passes and task is cancelled try: print(await asyncio.wait_for(task(2, 10), 0.01)) except asyncio.TimeoutError: print("timeout") + print(sep) # When timeout passes and task is cancelled, but task ignores the cancellation request try: print(await asyncio.wait_for(task_catch(), 0.1)) except asyncio.TimeoutError: print("TimeoutError") + print(sep) # When task raises an exception try: print(await asyncio.wait_for(task_raise(), 1)) except ValueError: print("ValueError") + print(sep) # Timeout of None means wait forever print(await asyncio.wait_for(task(3, 0.1), None)) + print(sep) + + # When task is cancelled by another task + t = asyncio.create_task(task(4, 10)) + asyncio.create_task(task_cancel_other(0.01, t)) + try: + print(await asyncio.wait_for(t, 1)) + except asyncio.CancelledError as er: + print(repr(er)) + print(sep) + + # When wait_for gets cancelled + t = asyncio.create_task(task_wait_for_cancel(4, 1, 2)) + await asyncio.sleep(0.01) + t.cancel() + await asyncio.sleep(0.01) + print(sep) + + # When wait_for gets cancelled and awaited task ignores the cancellation request + t = asyncio.create_task(task_wait_for_cancel_ignore(2)) + await asyncio.sleep(0.01) + t.cancel() + await asyncio.sleep(0.01) + print(sep) print("finish") diff --git a/tests/extmod/uasyncio_wait_for.py.exp b/tests/extmod/uasyncio_wait_for.py.exp index 41a5f2481e908..a4201d31ffdb8 100644 --- a/tests/extmod/uasyncio_wait_for.py.exp +++ b/tests/extmod/uasyncio_wait_for.py.exp @@ -1,15 +1,35 @@ task start 1 task end 1 2 +---------- task start 2 timeout +---------- task_catch start ignore cancel task_catch done TimeoutError +---------- task start ValueError +---------- task start 3 task end 3 6 +---------- +task start 4 +task_cancel_other start +task_cancel_other cancel +CancelledError() +---------- +task_wait_for_cancel start +task start 4 +task_wait_for_cancel cancelled +---------- +task_wait_for_cancel_ignore start +task_catch start +task_wait_for_cancel_ignore cancelled +ignore cancel +task_catch done +---------- finish diff --git a/tests/extmod/uasyncio_wait_for_fwd.py b/tests/extmod/uasyncio_wait_for_fwd.py new file mode 100644 index 0000000000000..33738085ce4a5 --- /dev/null +++ b/tests/extmod/uasyncio_wait_for_fwd.py @@ -0,0 +1,60 @@ +# Test asyncio.wait_for, with forwarding cancellation + +try: + import uasyncio as asyncio +except ImportError: + try: + import asyncio + except ImportError: + print("SKIP") + raise SystemExit + + +async def awaiting(t, return_if_fail): + try: + print("awaiting started") + await asyncio.sleep(t) + except asyncio.CancelledError as er: + # CPython wait_for raises CancelledError inside task but TimeoutError in wait_for + print("awaiting canceled") + if return_if_fail: + return False # return has no effect if Cancelled + else: + raise er + except Exception as er: + print("caught exception", er) + raise er + + +async def test_cancellation_forwarded(catch, catch_inside): + print("----------") + + async def wait(): + try: + await asyncio.wait_for(awaiting(2, catch_inside), 1) + except asyncio.TimeoutError as er: + print("Got timeout error") + raise er + except asyncio.CancelledError as er: + print("Got canceled") + if not catch: + raise er + + async def cancel(t): + print("cancel started") + await asyncio.sleep(0.01) + print("cancel wait()") + t.cancel() + + t = asyncio.create_task(wait()) + k = asyncio.create_task(cancel(t)) + try: + await t + except asyncio.CancelledError: + print("waiting got cancelled") + + +asyncio.run(test_cancellation_forwarded(False, False)) +asyncio.run(test_cancellation_forwarded(False, True)) +asyncio.run(test_cancellation_forwarded(True, True)) +asyncio.run(test_cancellation_forwarded(True, False)) diff --git a/tests/extmod/uasyncio_wait_for_fwd.py.exp b/tests/extmod/uasyncio_wait_for_fwd.py.exp new file mode 100644 index 0000000000000..9f22f1a7d1e88 --- /dev/null +++ b/tests/extmod/uasyncio_wait_for_fwd.py.exp @@ -0,0 +1,26 @@ +---------- +cancel started +awaiting started +cancel wait() +Got canceled +awaiting canceled +waiting got cancelled +---------- +cancel started +awaiting started +cancel wait() +Got canceled +awaiting canceled +waiting got cancelled +---------- +cancel started +awaiting started +cancel wait() +Got canceled +awaiting canceled +---------- +cancel started +awaiting started +cancel wait() +Got canceled +awaiting canceled diff --git a/tests/run-tests b/tests/run-tests index 23c30d3c3cb55..cb5b5cd0a5676 100755 --- a/tests/run-tests +++ b/tests/run-tests @@ -434,7 +434,10 @@ def run_tests(pyb, tests, args, result_dir): skip_tests.add('basics/scope_implicit.py') # requires checking for unbound local skip_tests.add('basics/try_finally_return2.py') # requires raise_varargs skip_tests.add('basics/unboundlocal.py') # requires checking for unbound local + skip_tests.add('extmod/uasyncio_event.py') # unknown issue skip_tests.add('extmod/uasyncio_lock.py') # requires async with + skip_tests.add('extmod/uasyncio_micropython.py') # unknown issue + skip_tests.add('extmod/uasyncio_wait_for.py') # unknown issue skip_tests.add('misc/features.py') # requires raise_varargs skip_tests.add('misc/print_exception.py') # because native doesn't have proper traceback info skip_tests.add('misc/sys_exc_info.py') # sys.exc_info() is not supported for native From f2a9a0ac413cc337ccd78bbb26cd67cee7b1e210 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Wed, 25 Nov 2020 09:28:41 +1100 Subject: [PATCH 105/179] extmod/nimble: Fail read if the characteristic is too big. Signed-off-by: Jim Mussared --- extmod/nimble/modbluetooth_nimble.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index bbff26b5354e9..e4a048e3d3df4 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -608,7 +608,9 @@ static int characteristic_access_cb(uint16_t conn_handle, uint16_t value_handle, return BLE_ATT_ERR_ATTR_NOT_FOUND; } - os_mbuf_append(ctxt->om, entry->data, entry->data_len); + if (os_mbuf_append(ctxt->om, entry->data, entry->data_len)) { + return BLE_ATT_ERR_INSUFFICIENT_RES; + } return 0; case BLE_GATT_ACCESS_OP_WRITE_CHR: @@ -625,6 +627,8 @@ static int characteristic_access_cb(uint16_t conn_handle, uint16_t value_handle, entry->data_len = MIN(entry->data_alloc, OS_MBUF_PKTLEN(ctxt->om) + offset); os_mbuf_copydata(ctxt->om, 0, entry->data_len - offset, entry->data + offset); + // TODO: Consider failing with BLE_ATT_ERR_INSUFFICIENT_RES if the buffer is full. + mp_bluetooth_gatts_on_write(conn_handle, value_handle); return 0; From c70665fb0bc1b4d755abda422d221cc0a72e91da Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sat, 14 Nov 2020 19:40:25 +1100 Subject: [PATCH 106/179] extmod/modbluetooth: Add _IRQ_CONNECTION_UPDATE event. This allows the application to be notified of changes to the connection interval, connection latency and supervision timeout. Signed-off-by: Jim Mussared --- extmod/btstack/modbluetooth_btstack.c | 40 ++++++++++++++++++--------- extmod/modbluetooth.c | 21 ++++++++++++++ extmod/modbluetooth.h | 5 ++++ extmod/nimble/modbluetooth_nimble.c | 25 +++++++++++++---- 4 files changed, 72 insertions(+), 19 deletions(-) diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index 97dd2cbb53f5c..9faae26e46929 100644 --- a/extmod/btstack/modbluetooth_btstack.c +++ b/extmod/btstack/modbluetooth_btstack.c @@ -295,20 +295,34 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t if (event_type == HCI_EVENT_LE_META) { DEBUG_printf(" --> hci le meta\n"); - if (hci_event_le_meta_get_subevent_code(packet) == HCI_SUBEVENT_LE_CONNECTION_COMPLETE) { - uint16_t conn_handle = hci_subevent_le_connection_complete_get_connection_handle(packet); - uint8_t addr_type = hci_subevent_le_connection_complete_get_peer_address_type(packet); - bd_addr_t addr; - hci_subevent_le_connection_complete_get_peer_address(packet, addr); - uint16_t irq_event; - if (hci_subevent_le_connection_complete_get_role(packet) == 0) { - // Master role. - irq_event = MP_BLUETOOTH_IRQ_PERIPHERAL_CONNECT; - } else { - // Slave role. - irq_event = MP_BLUETOOTH_IRQ_CENTRAL_CONNECT; + switch (hci_event_le_meta_get_subevent_code(packet)) { + case HCI_SUBEVENT_LE_CONNECTION_COMPLETE: { + uint16_t conn_handle = hci_subevent_le_connection_complete_get_connection_handle(packet); + uint8_t addr_type = hci_subevent_le_connection_complete_get_peer_address_type(packet); + bd_addr_t addr; + hci_subevent_le_connection_complete_get_peer_address(packet, addr); + uint16_t irq_event; + if (hci_subevent_le_connection_complete_get_role(packet) == 0) { + // Master role. + irq_event = MP_BLUETOOTH_IRQ_PERIPHERAL_CONNECT; + } else { + // Slave role. + irq_event = MP_BLUETOOTH_IRQ_CENTRAL_CONNECT; + } + mp_bluetooth_gap_on_connected_disconnected(irq_event, conn_handle, addr_type, addr); + break; + } + case HCI_SUBEVENT_LE_CONNECTION_UPDATE_COMPLETE: { + uint8_t status = hci_subevent_le_connection_update_complete_get_status(packet); + uint16_t conn_handle = hci_subevent_le_connection_update_complete_get_connection_handle(packet); + uint16_t conn_interval = hci_subevent_le_connection_update_complete_get_conn_interval(packet); + uint16_t conn_latency = hci_subevent_le_connection_update_complete_get_conn_latency(packet); + uint16_t supervision_timeout = hci_subevent_le_connection_update_complete_get_supervision_timeout(packet); + DEBUG_printf("- LE Connection %04x: connection update - connection interval %u.%02u ms, latency %u, timeout %u\n", + conn_handle, conn_interval * 125 / 100, 25 * (conn_interval & 3), conn_latency, supervision_timeout); + mp_bluetooth_gap_on_connection_update(conn_handle, conn_interval, conn_latency, supervision_timeout, status); + break; } - mp_bluetooth_gap_on_connected_disconnected(irq_event, conn_handle, addr_type, addr); } } else if (event_type == BTSTACK_EVENT_STATE) { uint8_t state = btstack_event_state_get_state(packet); diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 2cff386f125d5..dc1084acc13dc 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -979,6 +979,9 @@ STATIC mp_obj_t bluetooth_ble_invoke_irq(mp_obj_t none_in) { if (event == MP_BLUETOOTH_IRQ_CENTRAL_CONNECT || event == MP_BLUETOOTH_IRQ_PERIPHERAL_CONNECT || event == MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT || event == MP_BLUETOOTH_IRQ_PERIPHERAL_DISCONNECT) { // conn_handle, addr_type, addr ringbuf_extract(&o->ringbuf, data_tuple, 1, 1, &o->irq_data_addr, 0, NULL, NULL); + } else if (event == MP_BLUETOOTH_IRQ_CONNECTION_UPDATE) { + // conn_handle, conn_interval, conn_latency, supervision_timeout, status + ringbuf_extract(&o->ringbuf, data_tuple, 5, 0, NULL, 0, NULL, NULL); } else if (event == MP_BLUETOOTH_IRQ_GATTS_WRITE) { // conn_handle, value_handle ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, NULL, NULL); @@ -1093,6 +1096,11 @@ void mp_bluetooth_gap_on_connected_disconnected(uint8_t event, uint16_t conn_han invoke_irq_handler(event, &conn_handle, 1, &addr_type, 1, addr, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); } +void mp_bluetooth_gap_on_connection_update(uint16_t conn_handle, uint16_t conn_interval, uint16_t conn_latency, uint16_t supervision_timeout, uint16_t status) { + uint16_t args[] = {conn_handle, conn_interval, conn_latency, supervision_timeout, status}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_CONNECTION_UPDATE, args, 5, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); +} + void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle) { uint16_t args[] = {conn_handle, value_handle}; invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_WRITE, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); @@ -1276,6 +1284,19 @@ void mp_bluetooth_gap_on_connected_disconnected(uint8_t event, uint16_t conn_han schedule_ringbuf(atomic_state); } +void mp_bluetooth_gap_on_connection_update(uint16_t conn_handle, uint16_t conn_interval, uint16_t conn_latency, uint16_t supervision_timeout, uint16_t status) { + MICROPY_PY_BLUETOOTH_ENTER + mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); + if (enqueue_irq(o, 2 + 2 + 2 + 2 + 2, MP_BLUETOOTH_IRQ_CONNECTION_UPDATE)) { + ringbuf_put16(&o->ringbuf, conn_handle); + ringbuf_put16(&o->ringbuf, conn_interval); + ringbuf_put16(&o->ringbuf, conn_latency); + ringbuf_put16(&o->ringbuf, supervision_timeout); + ringbuf_put16(&o->ringbuf, status); + } + schedule_ringbuf(atomic_state); +} + void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle) { MICROPY_PY_BLUETOOTH_ENTER mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 9caddb0f3f89e..2619e71894d7c 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -114,6 +114,7 @@ #define MP_BLUETOOTH_IRQ_L2CAP_DISCONNECT (24) #define MP_BLUETOOTH_IRQ_L2CAP_RECV (25) #define MP_BLUETOOTH_IRQ_L2CAP_SEND_READY (26) +#define MP_BLUETOOTH_IRQ_CONNECTION_UPDATE (27) #define MP_BLUETOOTH_ADDRESS_MODE_PUBLIC (0) #define MP_BLUETOOTH_ADDRESS_MODE_RANDOM (1) @@ -151,6 +152,7 @@ _IRQ_L2CAP_CONNECT = const(23) _IRQ_L2CAP_DISCONNECT = const(24) _IRQ_L2CAP_RECV = const(25) _IRQ_L2CAP_SEND_READY = const(26) +_IRQ_GATTS_CONN_UPDATE = const(27) */ // bluetooth.UUID type. @@ -281,6 +283,9 @@ int mp_bluetooth_l2cap_recvinto(uint16_t conn_handle, uint16_t cid, uint8_t *buf // Notify modbluetooth that a connection/disconnection event has occurred. void mp_bluetooth_gap_on_connected_disconnected(uint8_t event, uint16_t conn_handle, uint8_t addr_type, const uint8_t *addr); +// Call this when any connection parameters have been changed. +void mp_bluetooth_gap_on_connection_update(uint16_t conn_handle, uint16_t conn_interval, uint16_t conn_latency, uint16_t supervision_timeout, uint16_t status); + // Call this when a characteristic is written to. void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle); diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index e4a048e3d3df4..3983d1869082b 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -303,6 +303,19 @@ STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { } break; } + + case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: + DEBUG_printf("gap_event_cb: phy update: %d\n", event->phy_updated.tx_phy); + break; + + case BLE_GAP_EVENT_CONN_UPDATE: { + DEBUG_printf("gap_event_cb: connection update: status=%d\n", event->conn_update.status); + struct ble_gap_conn_desc desc; + if (ble_gap_conn_find(event->conn_update.conn_handle, &desc) == 0) { + mp_bluetooth_gap_on_connection_update(event->conn_update.conn_handle, desc.conn_itvl, desc.conn_latency, desc.supervision_timeout, event->conn_update.status == 0 ? 0 : 1); + } + break; + } } return 0; } @@ -938,13 +951,13 @@ STATIC int peripheral_gap_event_cb(struct ble_gap_event *event, void *arg) { break; } - case BLE_GAP_EVENT_CONN_UPDATE: - // TODO - break; - - case BLE_GAP_EVENT_CONN_UPDATE_REQ: - // TODO + case BLE_GAP_EVENT_CONN_UPDATE: { + DEBUG_printf("peripheral_gap_event_cb: connection update: status=%d\n", event->conn_update.status); + if (ble_gap_conn_find(event->conn_update.conn_handle, &desc) == 0) { + mp_bluetooth_gap_on_connection_update(event->conn_update.conn_handle, desc.conn_itvl, desc.conn_latency, desc.supervision_timeout, event->conn_update.status == 0 ? 0 : 1); + } break; + } case BLE_GAP_EVENT_MTU: { if (event->mtu.channel_id == BLE_L2CAP_CID_ATT) { From 7a9aa49595e1c523a5765397db135ff8f00515b1 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 24 Nov 2020 23:23:51 +1100 Subject: [PATCH 107/179] docs/library/ubluetooth.rst: Add _IRQ_CONNECTION_UDPATE docs. Signed-off-by: Jim Mussared --- docs/library/ubluetooth.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/library/ubluetooth.rst b/docs/library/ubluetooth.rst index 35ac13ad56fa9..73547c356aa25 100644 --- a/docs/library/ubluetooth.rst +++ b/docs/library/ubluetooth.rst @@ -199,6 +199,9 @@ Event Handling # A previous l2cap_send that returned False has now completed and the channel is ready to send again. # If status is non-zero, then the transmit buffer overflowed and the application should re-send the data. conn_handle, cid, status = data + elif event == _IRQ_CONNECTION_UPDATE: + # The remote device has updated connection parameters. + conn_handle, conn_interval, conn_latency, supervision_timeout, status = data The event codes are:: @@ -229,6 +232,7 @@ The event codes are:: _IRQ_L2CAP_DISCONNECT = const(24) _IRQ_L2CAP_RECV = const(25) _IRQ_L2CAP_SEND_READY = const(26) + _IRQ_CONNECTION_UPDATE = const(27) In order to save space in the firmware, these constants are not included on the :mod:`ubluetooth` module. Add the ones that you need from the list above to your From 1697ff335db523ff0809051d42871beb9c86012d Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Thu, 27 Aug 2020 09:13:25 +1000 Subject: [PATCH 108/179] extmod/modbluetooth: Allow setting char/desc enc/auth options. This widens the characteristic/descriptor flags to 16-bit, to allow setting encryption/authentication requirements. Sets the required flags for NimBLE and btstack implementations. The BLE.FLAG_* constants will eventually be deprecated in favour of copy and paste Python constants (like the IRQs). Signed-off-by: Jim Mussared --- examples/bluetooth/ble_simple_peripheral.py | 9 ++++- examples/bluetooth/ble_temperature.py | 6 ++- examples/bluetooth/ble_uart_peripheral.py | 7 +++- extmod/btstack/modbluetooth_btstack.c | 36 +++++++++++++---- extmod/modbluetooth.c | 8 ++-- extmod/modbluetooth.h | 44 ++++++++++++++++++--- extmod/nimble/modbluetooth_nimble.c | 6 ++- 7 files changed, 93 insertions(+), 23 deletions(-) diff --git a/examples/bluetooth/ble_simple_peripheral.py b/examples/bluetooth/ble_simple_peripheral.py index d2b134e7149e9..0ebe431764c93 100644 --- a/examples/bluetooth/ble_simple_peripheral.py +++ b/examples/bluetooth/ble_simple_peripheral.py @@ -12,14 +12,19 @@ _IRQ_CENTRAL_DISCONNECT = const(2) _IRQ_GATTS_WRITE = const(3) +_FLAG_READ = const(0x0002) +_FLAG_WRITE_NO_RESPONSE = const(0x0004) +_FLAG_WRITE = const(0x0008) +_FLAG_NOTIFY = const(0x0010) + _UART_UUID = bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E") _UART_TX = ( bluetooth.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"), - bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY, + _FLAG_READ | _FLAG_NOTIFY, ) _UART_RX = ( bluetooth.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"), - bluetooth.FLAG_WRITE | bluetooth.FLAG_WRITE_NO_RESPONSE, + _FLAG_WRITE | _FLAG_WRITE_NO_RESPONSE, ) _UART_SERVICE = ( _UART_UUID, diff --git a/examples/bluetooth/ble_temperature.py b/examples/bluetooth/ble_temperature.py index d375a62ffb8ac..e6378ebee7964 100644 --- a/examples/bluetooth/ble_temperature.py +++ b/examples/bluetooth/ble_temperature.py @@ -15,12 +15,16 @@ _IRQ_CENTRAL_DISCONNECT = const(2) _IRQ_GATTS_INDICATE_DONE = const(20) +_FLAG_READ = const(0x0002) +_FLAG_NOTIFY = const(0x0010) +_FLAG_INDICATE = const(0x0020) + # org.bluetooth.service.environmental_sensing _ENV_SENSE_UUID = bluetooth.UUID(0x181A) # org.bluetooth.characteristic.temperature _TEMP_CHAR = ( bluetooth.UUID(0x2A6E), - bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY | bluetooth.FLAG_INDICATE, + _FLAG_READ | _FLAG_NOTIFY | _FLAG_INDICATE, ) _ENV_SENSE_SERVICE = ( _ENV_SENSE_UUID, diff --git a/examples/bluetooth/ble_uart_peripheral.py b/examples/bluetooth/ble_uart_peripheral.py index 6d167a871a75e..b4e352be9f6dd 100644 --- a/examples/bluetooth/ble_uart_peripheral.py +++ b/examples/bluetooth/ble_uart_peripheral.py @@ -9,14 +9,17 @@ _IRQ_CENTRAL_DISCONNECT = const(2) _IRQ_GATTS_WRITE = const(3) +_FLAG_WRITE = const(0x0008) +_FLAG_NOTIFY = const(0x0010) + _UART_UUID = bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E") _UART_TX = ( bluetooth.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"), - bluetooth.FLAG_NOTIFY, + _FLAG_NOTIFY, ) _UART_RX = ( bluetooth.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"), - bluetooth.FLAG_WRITE, + _FLAG_WRITE, ) _UART_SERVICE = ( _UART_UUID, diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index 9faae26e46929..da9a6f732bf4c 100644 --- a/extmod/btstack/modbluetooth_btstack.c +++ b/extmod/btstack/modbluetooth_btstack.c @@ -893,7 +893,30 @@ STATIC inline uint16_t get_uuid16(const mp_obj_bluetooth_uuid_t *uuid) { return (uuid->data[1] << 8) | uuid->data[0]; } -int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, mp_obj_bluetooth_uuid_t **characteristic_uuids, uint8_t *characteristic_flags, mp_obj_bluetooth_uuid_t **descriptor_uuids, uint8_t *descriptor_flags, uint8_t *num_descriptors, uint16_t *handles, size_t num_characteristics) { +// Map MP_BLUETOOTH_CHARACTERISTIC_FLAG_ values to btstack read/write permission values. +STATIC void get_characteristic_permissions(uint16_t flags, uint16_t *read_permission, uint16_t *write_permission) { + if (flags & MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ_ENCRYPTED) { + *read_permission = ATT_SECURITY_ENCRYPTED; + } else if (flags & MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ_AUTHENTICATED) { + *read_permission = ATT_SECURITY_AUTHENTICATED; + } else if (flags & MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ_AUTHORIZED) { + *read_permission = ATT_SECURITY_AUTHORIZED; + } else { + *read_permission = ATT_SECURITY_NONE; + } + + if (flags & MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_ENCRYPTED) { + *write_permission = ATT_SECURITY_ENCRYPTED; + } else if (flags & MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_AUTHENTICATED) { + *write_permission = ATT_SECURITY_AUTHENTICATED; + } else if (flags & MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_AUTHORIZED) { + *write_permission = ATT_SECURITY_AUTHORIZED; + } else { + *write_permission = ATT_SECURITY_NONE; + } +} + +int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, mp_obj_bluetooth_uuid_t **characteristic_uuids, uint16_t *characteristic_flags, mp_obj_bluetooth_uuid_t **descriptor_uuids, uint16_t *descriptor_flags, uint8_t *num_descriptors, uint16_t *handles, size_t num_characteristics) { DEBUG_printf("mp_bluetooth_gatts_register_service\n"); // Note: btstack expects BE UUIDs (which it immediately convertes to LE). // So we have to convert all our modbluetooth LE UUIDs to BE just for the att_db_util_add_* methods (using get_uuid16 above, and reverse_128 from btstackutil.h). @@ -916,9 +939,9 @@ int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, m static uint8_t cccb_buf[2] = {0}; for (size_t i = 0; i < num_characteristics; ++i) { - uint16_t props = characteristic_flags[i] | ATT_PROPERTY_DYNAMIC; - uint16_t read_permission = ATT_SECURITY_NONE; - uint16_t write_permission = ATT_SECURITY_NONE; + uint16_t props = (characteristic_flags[i] & 0x7f) | ATT_PROPERTY_DYNAMIC; + uint16_t read_permission, write_permission; + get_characteristic_permissions(characteristic_flags[i], &read_permission, &write_permission); if (characteristic_uuids[i]->type == MP_BLUETOOTH_UUID_TYPE_16) { handles[handle_index] = att_db_util_add_characteristic_uuid16(get_uuid16(characteristic_uuids[i]), props, read_permission, write_permission, NULL, 0); } else if (characteristic_uuids[i]->type == MP_BLUETOOTH_UUID_TYPE_128) { @@ -942,9 +965,8 @@ int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, m ++handle_index; for (size_t j = 0; j < num_descriptors[i]; ++j) { - props = descriptor_flags[descriptor_index] | ATT_PROPERTY_DYNAMIC; - read_permission = ATT_SECURITY_NONE; - write_permission = ATT_SECURITY_NONE; + props = (descriptor_flags[descriptor_index] & 0x7f) | ATT_PROPERTY_DYNAMIC; + get_characteristic_permissions(descriptor_flags[descriptor_index], &read_permission, &write_permission); if (descriptor_uuids[descriptor_index]->type == MP_BLUETOOTH_UUID_TYPE_16) { handles[handle_index] = att_db_util_add_descriptor_uuid16(get_uuid16(descriptor_uuids[descriptor_index]), props, read_permission, write_permission, NULL, 0); diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index dc1084acc13dc..1e67c8ce32749 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -461,11 +461,11 @@ STATIC int bluetooth_gatts_register_service(mp_obj_t uuid_in, mp_obj_t character // Lists of characteristic uuids and flags. mp_obj_bluetooth_uuid_t **characteristic_uuids = m_new(mp_obj_bluetooth_uuid_t *, len); - uint8_t *characteristic_flags = m_new(uint8_t, len); + uint16_t *characteristic_flags = m_new(uint16_t, len); // Flattened list of descriptor uuids and flags. Grows (realloc) as more descriptors are encountered. mp_obj_bluetooth_uuid_t **descriptor_uuids = NULL; - uint8_t *descriptor_flags = NULL; + uint16_t *descriptor_flags = NULL; // How many descriptors in the flattened list per characteristic. uint8_t *num_descriptors = m_new(uint8_t, len); @@ -506,7 +506,7 @@ STATIC int bluetooth_gatts_register_service(mp_obj_t uuid_in, mp_obj_t character // Grow the flattened uuids and flags arrays with this many more descriptors. descriptor_uuids = m_renew(mp_obj_bluetooth_uuid_t *, descriptor_uuids, descriptor_index, descriptor_index + num_descriptors[characteristic_index]); - descriptor_flags = m_renew(uint8_t, descriptor_flags, descriptor_index, descriptor_index + num_descriptors[characteristic_index]); + descriptor_flags = m_renew(uint16_t, descriptor_flags, descriptor_index, descriptor_index + num_descriptors[characteristic_index]); // Also grow the handles array. *handles = m_renew(uint16_t, *handles, *num_handles, *num_handles + num_descriptors[characteristic_index]); @@ -894,6 +894,8 @@ STATIC const mp_rom_map_elem_t mp_module_bluetooth_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ubluetooth) }, { MP_ROM_QSTR(MP_QSTR_BLE), MP_ROM_PTR(&mp_type_bluetooth_ble) }, { MP_ROM_QSTR(MP_QSTR_UUID), MP_ROM_PTR(&mp_type_bluetooth_uuid) }, + + // TODO: Deprecate these flags (recommend copying the constants from modbluetooth.h instead). { MP_ROM_QSTR(MP_QSTR_FLAG_READ), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ) }, { MP_ROM_QSTR(MP_QSTR_FLAG_WRITE), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE) }, { MP_ROM_QSTR(MP_QSTR_FLAG_NOTIFY), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY) }, diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 2619e71894d7c..0d4296626b589 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -71,12 +71,28 @@ // Advertisement packet lengths #define MP_BLUETOOTH_GAP_ADV_MAX_LEN (32) +// Basic characteristic/descriptor flags. // These match the spec values for these flags so can be passed directly to the stack. -#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ (1 << 1) -#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_NO_RESPONSE (1 << 2) -#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE (1 << 3) -#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY (1 << 4) -#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE (1 << 5) +#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_BROADCAST (0x0001) +#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ (0x0002) +#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_NO_RESPONSE (0x0004) +#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE (0x0008) +#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY (0x0010) +#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE (0x0020) +#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_AUTHENTICATED_SIGNED_WRITE (0x0040) + +// TODO: NimBLE and BlueKitchen disagree on this one. +// #define MP_BLUETOOTH_CHARACTERISTIC_FLAG_RELIABLE_WRITE (0x0080) + +// Extended flags for security and privacy. +// These match NimBLE but might require mapping in the bindings for other stacks. +#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_AUX_WRITE (0x0100) +#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ_ENCRYPTED (0x0200) +#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ_AUTHENTICATED (0x0400) +#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ_AUTHORIZED (0x0800) +#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_ENCRYPTED (0x1000) +#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_AUTHENTICATED (0x2000) +#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_AUTHORIZED (0x4000) // For mp_bluetooth_gattc_write, the mode parameter #define MP_BLUETOOTH_WRITE_MODE_NO_RESPONSE (0) @@ -153,6 +169,22 @@ _IRQ_L2CAP_DISCONNECT = const(24) _IRQ_L2CAP_RECV = const(25) _IRQ_L2CAP_SEND_READY = const(26) _IRQ_GATTS_CONN_UPDATE = const(27) + +_FLAG_BROADCAST = const(0x0001) +_FLAG_READ = const(0x0002) +_FLAG_WRITE_NO_RESPONSE = const(0x0004) +_FLAG_WRITE = const(0x0008) +_FLAG_NOTIFY = const(0x0010) +_FLAG_INDICATE = const(0x0020) +_FLAG_AUTHENTICATED_SIGNED_WRITE = const(0x0040) + +_FLAG_AUX_WRITE = const(0x0100) +_FLAG_READ_ENCRYPTED = const(0x0200) +_FLAG_READ_AUTHENTICATED = const(0x0400) +_FLAG_READ_AUTHORIZED = const(0x0800) +_FLAG_WRITE_ENCRYPTED = const(0x1000) +_FLAG_WRITE_AUTHENTICATED = const(0x2000) +_FLAG_WRITE_AUTHORIZED = const(0x4000) */ // bluetooth.UUID type. @@ -214,7 +246,7 @@ void mp_bluetooth_gap_advertise_stop(void); int mp_bluetooth_gatts_register_service_begin(bool append); // Add a service with the given list of characteristics to the queue to be registered. // The value_handles won't be valid until after mp_bluetooth_register_service_end is called. -int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, mp_obj_bluetooth_uuid_t **characteristic_uuids, uint8_t *characteristic_flags, mp_obj_bluetooth_uuid_t **descriptor_uuids, uint8_t *descriptor_flags, uint8_t *num_descriptors, uint16_t *handles, size_t num_characteristics); +int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, mp_obj_bluetooth_uuid_t **characteristic_uuids, uint16_t *characteristic_flags, mp_obj_bluetooth_uuid_t **descriptor_uuids, uint16_t *descriptor_flags, uint8_t *num_descriptors, uint16_t *handles, size_t num_characteristics); // Register any queued services. int mp_bluetooth_gatts_register_service_end(void); diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 3983d1869082b..4484df937fc9e 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -685,7 +685,7 @@ int mp_bluetooth_gatts_register_service_end(void) { return 0; } -int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, mp_obj_bluetooth_uuid_t **characteristic_uuids, uint8_t *characteristic_flags, mp_obj_bluetooth_uuid_t **descriptor_uuids, uint8_t *descriptor_flags, uint8_t *num_descriptors, uint16_t *handles, size_t num_characteristics) { +int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, mp_obj_bluetooth_uuid_t **characteristic_uuids, uint16_t *characteristic_flags, mp_obj_bluetooth_uuid_t **descriptor_uuids, uint16_t *descriptor_flags, uint8_t *num_descriptors, uint16_t *handles, size_t num_characteristics) { if (MP_STATE_PORT(bluetooth_nimble_root_pointers)->n_services == MP_BLUETOOTH_NIMBLE_MAX_SERVICES) { return MP_E2BIG; } @@ -697,6 +697,7 @@ int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, m characteristics[i].uuid = create_nimble_uuid(characteristic_uuids[i], NULL); characteristics[i].access_cb = characteristic_access_cb; characteristics[i].arg = NULL; + // NimBLE flags match the MP_BLUETOOTH_CHARACTERISTIC_FLAG_ ones exactly (including the security/privacy options). characteristics[i].flags = characteristic_flags[i]; characteristics[i].min_key_size = 0; characteristics[i].val_handle = &handles[handle_index]; @@ -710,7 +711,8 @@ int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, m for (size_t j = 0; j < num_descriptors[i]; ++j) { descriptors[j].uuid = create_nimble_uuid(descriptor_uuids[descriptor_index], NULL); descriptors[j].access_cb = characteristic_access_cb; - descriptors[j].att_flags = descriptor_flags[descriptor_index]; + // NimBLE doesn't support security/privacy options on descriptors. + descriptors[j].att_flags = (uint8_t)descriptor_flags[descriptor_index]; descriptors[j].min_key_size = 0; // Unlike characteristic, Nimble doesn't provide an automatic way to remember the handle, so use the arg. descriptors[j].arg = &handles[handle_index]; From 89553997b8496aca96b46274c81fa6413d58f6bd Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 24 Nov 2020 22:40:14 +1100 Subject: [PATCH 109/179] docs/library/ubluetooth.rst: Update char/desc flags. Signed-off-by: Jim Mussared --- docs/library/ubluetooth.rst | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/docs/library/ubluetooth.rst b/docs/library/ubluetooth.rst index 73547c356aa25..434759b43250a 100644 --- a/docs/library/ubluetooth.rst +++ b/docs/library/ubluetooth.rst @@ -375,9 +375,9 @@ writes from a client to a given characteristic, use Each **descriptor** is a two-element tuple containing a UUID and a **flags** value. - The **flags** are a bitwise-OR combination of the - :data:`ubluetooth.FLAG_READ`, :data:`ubluetooth.FLAG_WRITE` and - :data:`ubluetooth.FLAG_NOTIFY` values defined below. + The **flags** are a bitwise-OR combination of the flags defined below. These + set both the behaviour of the characteristic (or descriptor) as well as the + security and privacy requirements. The return value is a list (one element per service) of tuples (each element is a value handle). Characteristics and descriptor handles are flattened @@ -401,6 +401,25 @@ writes from a client to a given characteristic, use **Note:** Advertising must be stopped before registering services. + Available flags for characteristics and descriptors are:: + + from micropython import const + _FLAG_BROADCAST = const(0x0001) + _FLAG_READ = const(0x0002) + _FLAG_WRITE_NO_RESPONSE = const(0x0004) + _FLAG_WRITE = const(0x0008) + _FLAG_NOTIFY = const(0x0010) + _FLAG_INDICATE = const(0x0020) + _FLAG_AUTHENTICATED_SIGNED_WRITE = const(0x0040) + + _FLAG_AUX_WRITE = const(0x0100) + _FLAG_READ_ENCRYPTED = const(0x0200) + _FLAG_READ_AUTHENTICATED = const(0x0400) + _FLAG_READ_AUTHORIZED = const(0x0800) + _FLAG_WRITE_ENCRYPTED = const(0x1000) + _FLAG_WRITE_AUTHENTICATED = const(0x2000) + _FLAG_WRITE_AUTHORIZED = const(0x4000) + .. method:: BLE.gatts_read(value_handle, /) Reads the local value for this handle (which has either been written by @@ -618,11 +637,3 @@ Constructor - A 16-bit integer. e.g. ``0x2908``. - A 128-bit UUID string. e.g. ``'6E400001-B5A3-F393-E0A9-E50E24DCCA9E'``. - - -Constants ---------- - -.. data:: ubluetooth.FLAG_READ - ubluetooth.FLAG_WRITE - ubluetooth.FLAG_NOTIFY From 60830bcba46dd649e396b605708274a2208c398d Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 24 Nov 2020 23:04:58 +1100 Subject: [PATCH 110/179] extmod/modbluetooth: Allow user-specified reason in read request IRQ. Instead of returning None/bool from the IRQ, return None/int (where a zero value means success). This mirrors how the L2CAP_ACCEPT return value works. Signed-off-by: Jim Mussared --- extmod/modbluetooth.c | 11 +++++++---- extmod/modbluetooth.h | 20 ++++++++++++++++++-- extmod/nimble/modbluetooth_nimble.c | 8 +++++--- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 1e67c8ce32749..ed983b7946107 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -1113,10 +1113,13 @@ void mp_bluetooth_gatts_on_indicate_complete(uint16_t conn_handle, uint16_t valu invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_INDICATE_DONE, args, 2, &status, 1, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); } -bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle) { +mp_int_t mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle) { uint16_t args[] = {conn_handle, value_handle}; mp_obj_t result = invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST, args, 2, NULL, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); - return result == mp_const_none || mp_obj_is_true(result); + // Return non-zero from IRQ handler to fail the read. + mp_int_t ret = 0; + mp_obj_get_int_maybe(result, &ret); + return ret; } void mp_bluetooth_gatts_on_mtu_exchanged(uint16_t conn_handle, uint16_t value) { @@ -1320,11 +1323,11 @@ void mp_bluetooth_gatts_on_indicate_complete(uint16_t conn_handle, uint16_t valu schedule_ringbuf(atomic_state); } -bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle) { +mp_int_t mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle) { (void)conn_handle; (void)value_handle; // This must be handled synchronously and therefore cannot implemented with the ringbuffer. - return true; + return MP_BLUETOOTH_GATTS_NO_ERROR; } void mp_bluetooth_gatts_on_mtu_exchanged(uint16_t conn_handle, uint16_t value) { diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 0d4296626b589..9856d4a689407 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -94,6 +94,14 @@ #define MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_AUTHENTICATED (0x2000) #define MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_AUTHORIZED (0x4000) +// Return values from _IRQ_GATTS_READ_REQUEST. +#define MP_BLUETOOTH_GATTS_NO_ERROR (0x00) +#define MP_BLUETOOTH_GATTS_ERROR_READ_NOT_PERMITTED (0x02) +#define MP_BLUETOOTH_GATTS_ERROR_WRITE_NOT_PERMITTED (0x03) +#define MP_BLUETOOTH_GATTS_ERROR_INSUFFICIENT_AUTHENTICATION (0x05) +#define MP_BLUETOOTH_GATTS_ERROR_INSUFFICIENT_AUTHORIZATION (0x08) +#define MP_BLUETOOTH_GATTS_ERROR_INSUFFICIENT_ENCRYPTION (0x0f) + // For mp_bluetooth_gattc_write, the mode parameter #define MP_BLUETOOTH_WRITE_MODE_NO_RESPONSE (0) #define MP_BLUETOOTH_WRITE_MODE_WITH_RESPONSE (1) @@ -185,6 +193,13 @@ _FLAG_READ_AUTHORIZED = const(0x0800) _FLAG_WRITE_ENCRYPTED = const(0x1000) _FLAG_WRITE_AUTHENTICATED = const(0x2000) _FLAG_WRITE_AUTHORIZED = const(0x4000) + +_GATTS_NO_ERROR = const(0x00) +_GATTS_ERROR_READ_NOT_PERMITTED = const(0x02) +_GATTS_ERROR_WRITE_NOT_PERMITTED = const(0x03) +_GATTS_ERROR_INSUFFICIENT_AUTHENTICATION = const(0x05) +_GATTS_ERROR_INSUFFICIENT_AUTHORIZATION = const(0x08) +_GATTS_ERROR_INSUFFICIENT_ENCRYPTION = const(0x0f) */ // bluetooth.UUID type. @@ -324,8 +339,9 @@ void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle); // Call this when an acknowledgment is received for an indication. void mp_bluetooth_gatts_on_indicate_complete(uint16_t conn_handle, uint16_t value_handle, uint8_t status); -// Call this when a characteristic is read from. Return false to deny the read. -bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle); +// Call this when a characteristic is read from (giving the handler a chance to update the stored value). +// Return 0 to allow the read, otherwise a non-zero rejection reason (see MP_BLUETOOTH_GATTS_ERROR_*). +mp_int_t mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle); // Call this when an MTU exchange completes. void mp_bluetooth_gatts_on_mtu_exchanged(uint16_t conn_handle, uint16_t value); diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 4484df937fc9e..e79dc2d4cb09c 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -608,12 +608,13 @@ static int characteristic_access_cb(uint16_t conn_handle, uint16_t value_handle, mp_bluetooth_gatts_db_entry_t *entry; switch (ctxt->op) { case BLE_GATT_ACCESS_OP_READ_CHR: - case BLE_GATT_ACCESS_OP_READ_DSC: + case BLE_GATT_ACCESS_OP_READ_DSC: { // Allow Python code to override (by using gatts_write), or deny (by returning false) the read. // Note this will be a no-op if the ringbuffer implementation is being used (i.e. the stack isn't // run in the scheduler). The ringbuffer is not used on STM32 and Unix-H4 only. - if (!mp_bluetooth_gatts_on_read_request(conn_handle, value_handle)) { - return BLE_ATT_ERR_READ_NOT_PERMITTED; + int req = mp_bluetooth_gatts_on_read_request(conn_handle, value_handle); + if (req) { + return req; } entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, value_handle); @@ -626,6 +627,7 @@ static int characteristic_access_cb(uint16_t conn_handle, uint16_t value_handle, } return 0; + } case BLE_GATT_ACCESS_OP_WRITE_CHR: case BLE_GATT_ACCESS_OP_WRITE_DSC: entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, value_handle); From 5e20f689ada17dcd750d516c957115fbb36c9435 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 24 Nov 2020 23:09:10 +1100 Subject: [PATCH 111/179] docs/library/ubluetooth.rst: Update read request IRQ docs. Signed-off-by: Jim Mussared --- docs/library/ubluetooth.rst | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/library/ubluetooth.rst b/docs/library/ubluetooth.rst index 434759b43250a..f65020d2aed6a 100644 --- a/docs/library/ubluetooth.rst +++ b/docs/library/ubluetooth.rst @@ -119,9 +119,9 @@ Event Handling # A client has written to this characteristic or descriptor. conn_handle, attr_handle = data elif event == _IRQ_GATTS_READ_REQUEST: - # A client has issued a read. Note: this is a hard IRQ. - # Return None to deny the read. - # Note: This event is not supported on ESP32. + # A client has issued a read. Note: this is only supported on STM32. + # Return a non-zero integer to deny the read (see below), or zero (or None) + # to accept the read. conn_handle, attr_handle = data elif event == _IRQ_SCAN_RESULT: # A single scan result. @@ -234,6 +234,15 @@ The event codes are:: _IRQ_L2CAP_SEND_READY = const(26) _IRQ_CONNECTION_UPDATE = const(27) +For the ``_IRQ_GATTS_READ_REQUEST`` event, the available return codes are:: + + _GATTS_NO_ERROR = const(0x00) + _GATTS_ERROR_READ_NOT_PERMITTED = const(0x02) + _GATTS_ERROR_WRITE_NOT_PERMITTED = const(0x03) + _GATTS_ERROR_INSUFFICIENT_AUTHENTICATION = const(0x05) + _GATTS_ERROR_INSUFFICIENT_AUTHORIZATION = const(0x08) + _GATTS_ERROR_INSUFFICIENT_ENCRYPTION = const(0x0f) + In order to save space in the firmware, these constants are not included on the :mod:`ubluetooth` module. Add the ones that you need from the list above to your program. @@ -420,6 +429,8 @@ writes from a client to a given characteristic, use _FLAG_WRITE_AUTHENTICATED = const(0x2000) _FLAG_WRITE_AUTHORIZED = const(0x4000) + As for the IRQs above, any required constants should be added to your Python code. + .. method:: BLE.gatts_read(value_handle, /) Reads the local value for this handle (which has either been written by From ac89267fef8b2396cf46c6ba19ccbe3d10acff9a Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Wed, 25 Nov 2020 19:26:56 +1100 Subject: [PATCH 112/179] extmod/modbluetooth: Add compile-config flag to enable pairing/bonding. Enable it on STM32/Unix NimBLE only (pairing/bonding requires synchronous events and full bindings). Signed-off-by: Jim Mussared --- extmod/modbluetooth.c | 4 ++++ extmod/modbluetooth.h | 6 ++++++ extmod/nimble/nimble.mk | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index ed983b7946107..b9b7c712481f8 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -47,6 +47,10 @@ #error l2cap channels require synchronous modbluetooth events #endif +#if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING && !MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS +#error pairing and bonding require synchronous modbluetooth events +#endif + #define MP_BLUETOOTH_CONNECT_DEFAULT_SCAN_DURATION_MS 2000 #define MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN 5 diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 9856d4a689407..2cb2c709c5345 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -54,6 +54,12 @@ #define MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS (0) #endif +// A port can optionally enable support for pairing and bonding. +// Requires MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS. +#ifndef MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING +#define MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING (0) +#endif + // This is used to protect the ringbuffer. // A port may no-op this if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS is enabled. #ifndef MICROPY_PY_BLUETOOTH_ENTER diff --git a/extmod/nimble/nimble.mk b/extmod/nimble/nimble.mk index 00a244d6eac77..ba094f16f871a 100644 --- a/extmod/nimble/nimble.mk +++ b/extmod/nimble/nimble.mk @@ -24,6 +24,11 @@ ifeq ($(MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY),0) # UART is also polled by the RX IRQ. CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS=1 +# Without the ringbuffer, and with the full implementation, we can also +# enable pairing and bonding. This requires both synchronous events and +# some customisation of the key store. +CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING=1 + NIMBLE_LIB_DIR = lib/mynewt-nimble LIB_SRC_C += $(addprefix $(NIMBLE_LIB_DIR)/, \ From 05fef8c6a4113bc05dd09ddd8d0bf7d136d59f39 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 4 Nov 2020 18:21:59 +1100 Subject: [PATCH 113/179] extmod/modbluetooth: Add _IRQ_ENCRYPTION_UPDATE event. This allows the application to be notified if any of encrypted, authenticated and bonded state change, as well as the encryption key size. Signed-off-by: Jim Mussared --- docs/library/ubluetooth.rst | 4 ++++ extmod/btstack/modbluetooth_btstack.c | 29 +++++++++++++++++++++++++++ extmod/modbluetooth.c | 7 +++++++ extmod/modbluetooth.h | 9 ++++++++- extmod/nimble/modbluetooth_nimble.c | 24 ++++++++++++++++++++++ 5 files changed, 72 insertions(+), 1 deletion(-) diff --git a/docs/library/ubluetooth.rst b/docs/library/ubluetooth.rst index f65020d2aed6a..d402c6a9ebb25 100644 --- a/docs/library/ubluetooth.rst +++ b/docs/library/ubluetooth.rst @@ -202,6 +202,9 @@ Event Handling elif event == _IRQ_CONNECTION_UPDATE: # The remote device has updated connection parameters. conn_handle, conn_interval, conn_latency, supervision_timeout, status = data + elif event == _IRQ_ENCRYPTION_UPDATE: + # The encryption state has changed (likely as a result of pairing or bonding). + conn_handle, encrypted, authenticated, bonded, key_size = data The event codes are:: @@ -233,6 +236,7 @@ The event codes are:: _IRQ_L2CAP_RECV = const(25) _IRQ_L2CAP_SEND_READY = const(26) _IRQ_CONNECTION_UPDATE = const(27) + _IRQ_ENCRYPTION_UPDATE = const(28) For the ``_IRQ_GATTS_READ_REQUEST`` event, the available return codes are:: diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index da9a6f732bf4c..825a9ab7b178e 100644 --- a/extmod/btstack/modbluetooth_btstack.c +++ b/extmod/btstack/modbluetooth_btstack.c @@ -359,6 +359,35 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t DEBUG_printf(" --> btstack # conns changed\n"); } else if (event_type == HCI_EVENT_VENDOR_SPECIFIC) { DEBUG_printf(" --> hci vendor specific\n"); + } else if (event_type == SM_EVENT_AUTHORIZATION_RESULT || + event_type == SM_EVENT_PAIRING_COMPLETE || + // event_type == GAP_EVENT_DEDICATED_BONDING_COMPLETED || // No conn_handle + event_type == HCI_EVENT_ENCRYPTION_CHANGE) { + DEBUG_printf(" --> enc/auth/pair/bond change\n", ); + #if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING + uint16_t conn_handle; + switch (event_type) { + case SM_EVENT_AUTHORIZATION_RESULT: + conn_handle = sm_event_authorization_result_get_handle(packet); + break; + case SM_EVENT_PAIRING_COMPLETE: + conn_handle = sm_event_pairing_complete_get_handle(packet); + break; + case HCI_EVENT_ENCRYPTION_CHANGE: + conn_handle = hci_event_encryption_change_get_connection_handle(packet); + break; + default: + return; + } + + hci_connection_t *hci_con = hci_connection_for_handle(conn_handle); + sm_connection_t *desc = &hci_con->sm_connection; + mp_bluetooth_gatts_on_encryption_update(conn_handle, + desc->sm_connection_encrypted, + desc->sm_connection_authenticated, + desc->sm_le_db_index != -1, + desc->sm_actual_encryption_key_size); + #endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING } else if (event_type == HCI_EVENT_DISCONNECTION_COMPLETE) { DEBUG_printf(" --> hci disconnect complete\n"); uint16_t conn_handle = hci_event_disconnection_complete_get_connection_handle(packet); diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index b9b7c712481f8..5d7de7b97fe06 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -1107,6 +1107,13 @@ void mp_bluetooth_gap_on_connection_update(uint16_t conn_handle, uint16_t conn_i invoke_irq_handler(MP_BLUETOOTH_IRQ_CONNECTION_UPDATE, args, 5, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); } +#if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING +void mp_bluetooth_gatts_on_encryption_update(uint16_t conn_handle, bool encrypted, bool authenticated, bool bonded, uint8_t key_size) { + uint8_t args[] = {encrypted, authenticated, bonded, key_size}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_ENCRYPTION_UPDATE, &conn_handle, 1, args, 4, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); +} +#endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING + void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle) { uint16_t args[] = {conn_handle, value_handle}; invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_WRITE, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 2cb2c709c5345..62ff6f2f9e325 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -145,6 +145,7 @@ #define MP_BLUETOOTH_IRQ_L2CAP_RECV (25) #define MP_BLUETOOTH_IRQ_L2CAP_SEND_READY (26) #define MP_BLUETOOTH_IRQ_CONNECTION_UPDATE (27) +#define MP_BLUETOOTH_IRQ_ENCRYPTION_UPDATE (28) #define MP_BLUETOOTH_ADDRESS_MODE_PUBLIC (0) #define MP_BLUETOOTH_ADDRESS_MODE_RANDOM (1) @@ -182,7 +183,8 @@ _IRQ_L2CAP_CONNECT = const(23) _IRQ_L2CAP_DISCONNECT = const(24) _IRQ_L2CAP_RECV = const(25) _IRQ_L2CAP_SEND_READY = const(26) -_IRQ_GATTS_CONN_UPDATE = const(27) +_IRQ_CONNECTION_UPDATE = const(27) +_IRQ_ENCRYPTION_UPDATE = const(28) _FLAG_BROADCAST = const(0x0001) _FLAG_READ = const(0x0002) @@ -339,6 +341,11 @@ void mp_bluetooth_gap_on_connected_disconnected(uint8_t event, uint16_t conn_han // Call this when any connection parameters have been changed. void mp_bluetooth_gap_on_connection_update(uint16_t conn_handle, uint16_t conn_interval, uint16_t conn_latency, uint16_t supervision_timeout, uint16_t status); +#if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING +// Call this when any connection encryption has been changed (e.g. during pairing). +void mp_bluetooth_gatts_on_encryption_update(uint16_t conn_handle, bool encrypted, bool authenticated, bool bonded, uint8_t key_size); +#endif + // Call this when a characteristic is written to. void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle); diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index e79dc2d4cb09c..312a564263fa2 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -316,6 +316,19 @@ STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { } break; } + + case BLE_GAP_EVENT_ENC_CHANGE: { + DEBUG_printf("gap_event_cb: enc change: status=%d\n", event->enc_change.status); + #if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING + struct ble_gap_conn_desc desc; + if (ble_gap_conn_find(event->enc_change.conn_handle, &desc) == 0) { + mp_bluetooth_gatts_on_encryption_update(event->conn_update.conn_handle, + desc.sec_state.encrypted, desc.sec_state.authenticated, + desc.sec_state.bonded, desc.sec_state.key_size); + } + #endif + break; + } } return 0; } @@ -971,6 +984,17 @@ STATIC int peripheral_gap_event_cb(struct ble_gap_event *event, void *arg) { break; } + case BLE_GAP_EVENT_ENC_CHANGE: { + DEBUG_printf("peripheral_gap_event_cb: enc change: status=%d\n", event->enc_change.status); + #if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING + if (ble_gap_conn_find(event->enc_change.conn_handle, &desc) == 0) { + mp_bluetooth_gatts_on_encryption_update(event->conn_update.conn_handle, + desc.sec_state.encrypted, desc.sec_state.authenticated, + desc.sec_state.bonded, desc.sec_state.key_size); + } + #endif + break; + } default: break; } From a1fcf301217b34ae74fbb937a9488be5bc6e61a5 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 24 Nov 2020 23:54:46 +1100 Subject: [PATCH 114/179] extmod/modbluetooth: Allow configuration of pairing/bonding parameters. This allows setting the security and MITM-protection requirements. Signed-off-by: Jim Mussared --- extmod/btstack/modbluetooth_btstack.c | 38 +++++++++++++++++++++++++++ extmod/modbluetooth.c | 22 ++++++++++++++++ extmod/modbluetooth.h | 24 +++++++++++++++++ extmod/nimble/modbluetooth_nimble.c | 18 +++++++++++++ extmod/nimble/syscfg/syscfg.h | 13 ++++----- 5 files changed, 109 insertions(+), 6 deletions(-) diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index 825a9ab7b178e..ae4bac00940fa 100644 --- a/extmod/btstack/modbluetooth_btstack.c +++ b/extmod/btstack/modbluetooth_btstack.c @@ -53,6 +53,11 @@ STATIC const uint16_t BTSTACK_GAP_DEVICE_NAME_HANDLE = 3; volatile int mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; +// sm_set_authentication_requirements is set-only, so cache current value. +#if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING +STATIC uint8_t mp_bluetooth_btstack_sm_auth_req = 0; +#endif + #define ERRNO_BLUETOOTH_NOT_ACTIVE MP_ENODEV STATIC int btstack_error_to_errno(int err) { @@ -795,6 +800,39 @@ void mp_bluetooth_set_address_mode(uint8_t addr_mode) { } } +#if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING +void mp_bluetooth_set_bonding(bool enabled) { + if (enabled) { + mp_bluetooth_btstack_sm_auth_req |= SM_AUTHREQ_BONDING; + } else { + mp_bluetooth_btstack_sm_auth_req &= ~SM_AUTHREQ_BONDING; + } + sm_set_authentication_requirements(mp_bluetooth_btstack_sm_auth_req); +} + +void mp_bluetooth_set_mitm_protection(bool enabled) { + if (enabled) { + mp_bluetooth_btstack_sm_auth_req |= SM_AUTHREQ_MITM_PROTECTION; + } else { + mp_bluetooth_btstack_sm_auth_req &= ~SM_AUTHREQ_MITM_PROTECTION; + } + sm_set_authentication_requirements(mp_bluetooth_btstack_sm_auth_req); +} + +void mp_bluetooth_set_le_secure(bool enabled) { + if (enabled) { + mp_bluetooth_btstack_sm_auth_req |= SM_AUTHREQ_SECURE_CONNECTION; + } else { + mp_bluetooth_btstack_sm_auth_req &= ~SM_AUTHREQ_SECURE_CONNECTION; + } + sm_set_authentication_requirements(mp_bluetooth_btstack_sm_auth_req); +} + +void mp_bluetooth_set_io_capability(uint8_t capability) { + sm_set_io_capabilities(capability); +} +#endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING + size_t mp_bluetooth_gap_get_device_name(const uint8_t **buf) { uint8_t *value = NULL; size_t value_len = 0; diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 5d7de7b97fe06..06e340c42d20a 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -387,6 +387,28 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map mp_bluetooth_set_address_mode(addr_mode); break; } + #if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING + case MP_QSTR_bond: { + bool bonding_enabled = mp_obj_is_true(e->value); + mp_bluetooth_set_bonding(bonding_enabled); + break; + } + case MP_QSTR_mitm: { + bool mitm_protection = mp_obj_is_true(e->value); + mp_bluetooth_set_mitm_protection(mitm_protection); + break; + } + case MP_QSTR_io: { + mp_int_t io_capability = mp_obj_get_int(e->value); + mp_bluetooth_set_io_capability(io_capability); + break; + } + case MP_QSTR_le_secure: { + bool le_secure_required = mp_obj_is_true(e->value); + mp_bluetooth_set_le_secure(le_secure_required); + break; + } + #endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING default: mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); } diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 62ff6f2f9e325..977453becd486 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -152,6 +152,13 @@ #define MP_BLUETOOTH_ADDRESS_MODE_RPA (2) #define MP_BLUETOOTH_ADDRESS_MODE_NRPA (3) +// These match the spec values, can be used directly by the stack. +#define MP_BLUETOOTH_IO_CAPABILITY_DISPLAY_ONLY (0) +#define MP_BLUETOOTH_IO_CAPABILITY_DISPLAY_YESNO (1) +#define MP_BLUETOOTH_IO_CAPABILITY_KEYBOARD_ONLY (2) +#define MP_BLUETOOTH_IO_CAPABILITY_NO_INPUT_OUTPUT (3) +#define MP_BLUETOOTH_IO_CAPABILITY_KEYBOARD_DISPLAY (4) + /* These aren't included in the module for space reasons, but can be used in your Python code if necessary. @@ -208,6 +215,12 @@ _GATTS_ERROR_WRITE_NOT_PERMITTED = const(0x03) _GATTS_ERROR_INSUFFICIENT_AUTHENTICATION = const(0x05) _GATTS_ERROR_INSUFFICIENT_AUTHORIZATION = const(0x08) _GATTS_ERROR_INSUFFICIENT_ENCRYPTION = const(0x0f) + +_IO_CAPABILITY_DISPLAY_ONLY = const(0) +_IO_CAPABILITY_DISPLAY_YESNO = const(1) +_IO_CAPABILITY_KEYBOARD_ONLY = const(2) +_IO_CAPABILITY_NO_INPUT_OUTPUT = const(3) +_IO_CAPABILITY_KEYBOARD_DISPLAY = const(4) */ // bluetooth.UUID type. @@ -254,6 +267,17 @@ void mp_bluetooth_get_current_address(uint8_t *addr_type, uint8_t *addr); // Sets the addressing mode to use. void mp_bluetooth_set_address_mode(uint8_t addr_mode); +#if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING +// Set bonding flag in pairing requests (i.e. persist security keys). +void mp_bluetooth_set_bonding(bool enabled); +// Require MITM protection. +void mp_bluetooth_set_mitm_protection(bool enabled); +// Require LE Secure pairing (rather than Legacy Pairing) +void mp_bluetooth_set_le_secure(bool enabled); +// I/O capabilities for authentication (see MP_BLUETOOTH_IO_CAPABILITY_*). +void mp_bluetooth_set_io_capability(uint8_t capability); +#endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING + // Get or set the GAP device name that will be used by service 0x1800, characteristic 0x2a00. size_t mp_bluetooth_gap_get_device_name(const uint8_t **buf); int mp_bluetooth_gap_set_device_name(const uint8_t *buf, size_t len); diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 312a564263fa2..852b9eac021c2 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -552,6 +552,24 @@ void mp_bluetooth_set_address_mode(uint8_t addr_mode) { } } +#if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING +void mp_bluetooth_set_bonding(bool enabled) { + ble_hs_cfg.sm_bonding = enabled; +} + +void mp_bluetooth_set_mitm_protection(bool enabled) { + ble_hs_cfg.sm_mitm = enabled; +} + +void mp_bluetooth_set_le_secure(bool enabled) { + ble_hs_cfg.sm_sc = enabled; +} + +void mp_bluetooth_set_io_capability(uint8_t capability) { + ble_hs_cfg.sm_io_cap = capability; +} +#endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING + size_t mp_bluetooth_gap_get_device_name(const uint8_t **buf) { const char *name = ble_svc_gap_device_name(); *buf = (const uint8_t *)name; diff --git a/extmod/nimble/syscfg/syscfg.h b/extmod/nimble/syscfg/syscfg.h index bef6b3b9210d4..a051a8fefd069 100644 --- a/extmod/nimble/syscfg/syscfg.h +++ b/extmod/nimble/syscfg/syscfg.h @@ -116,18 +116,19 @@ int nimble_sprintf(char *str, const char *fmt, ...); #define MYNEWT_VAL_BLE_MONITOR_UART_BUFFER_SIZE (64) #define MYNEWT_VAL_BLE_MONITOR_UART_DEV ("uart0") #define MYNEWT_VAL_BLE_RPA_TIMEOUT (300) -#define MYNEWT_VAL_BLE_SM_BONDING (0) -#define MYNEWT_VAL_BLE_SM_IO_CAP (BLE_HS_IO_NO_INPUT_OUTPUT) #define MYNEWT_VAL_BLE_SM_KEYPRESS (0) #define MYNEWT_VAL_BLE_SM_LEGACY (1) #define MYNEWT_VAL_BLE_SM_MAX_PROCS (1) -#define MYNEWT_VAL_BLE_SM_MITM (0) #define MYNEWT_VAL_BLE_SM_OOB_DATA_FLAG (0) -#define MYNEWT_VAL_BLE_SM_OUR_KEY_DIST (0) -#define MYNEWT_VAL_BLE_SM_SC (1) -#define MYNEWT_VAL_BLE_SM_THEIR_KEY_DIST (0) +#define MYNEWT_VAL_BLE_SM_OUR_KEY_DIST (7) +#define MYNEWT_VAL_BLE_SM_THEIR_KEY_DIST (7) #define MYNEWT_VAL_BLE_STORE_MAX_BONDS (3) #define MYNEWT_VAL_BLE_STORE_MAX_CCCDS (8) +// These can be overridden at runtime with ble.config(le_secure, mitm, bond, io). +#define MYNEWT_VAL_BLE_SM_SC (1) +#define MYNEWT_VAL_BLE_SM_MITM (0) +#define MYNEWT_VAL_BLE_SM_BONDING (0) +#define MYNEWT_VAL_BLE_SM_IO_CAP (BLE_HS_IO_NO_INPUT_OUTPUT) /*** nimble/host/services/gap */ #define MYNEWT_VAL_BLE_SVC_GAP_APPEARANCE (0) From f822557cbb79decb6947e1e35b8cddce18f4c06f Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 24 Nov 2020 23:59:06 +1100 Subject: [PATCH 115/179] docs/library/ubluetooth.rst: Add pairing/bonding config docs. Signed-off-by: Jim Mussared --- docs/library/ubluetooth.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/library/ubluetooth.rst b/docs/library/ubluetooth.rst index d402c6a9ebb25..29d3044ebf4d7 100644 --- a/docs/library/ubluetooth.rst +++ b/docs/library/ubluetooth.rst @@ -80,6 +80,24 @@ Configuration :meth:`gattc_exchange_mtu`. Use the ``_IRQ_MTU_EXCHANGED`` event to discover the MTU for a given connection. + - ``'bond'``: Sets whether bonding will be enabled during pairing. When + enabled, pairing requests will set the "bond" flag and the keys will be stored + by both devices. + + - ``'mitm'``: Sets whether MITM-protection is required for pairing. + + - ``'io'``: Sets the I/O capabilities of this device. + + Available options are:: + + _IO_CAPABILITY_DISPLAY_ONLY = const(0) + _IO_CAPABILITY_DISPLAY_YESNO = const(1) + _IO_CAPABILITY_KEYBOARD_ONLY = const(2) + _IO_CAPABILITY_NO_INPUT_OUTPUT = const(3) + _IO_CAPABILITY_KEYBOARD_DISPLAY = const(4) + + - ``'le_secure'``: Sets whether "LE Secure" pairing is required. Default is "Legacy Pairing". + Event Handling -------------- From 801e8ffacff0d2ed15e82136db2a8a45afbfab7b Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Wed, 25 Nov 2020 00:06:16 +1100 Subject: [PATCH 116/179] extmod/modbluetooth: Add gap_pair(conn_handle) func to intiate pairing. Signed-off-by: Jim Mussared --- extmod/btstack/modbluetooth_btstack.c | 8 ++++++++ extmod/modbluetooth.c | 12 ++++++++++++ extmod/modbluetooth.h | 5 +++++ extmod/nimble/modbluetooth_nimble.c | 7 +++++++ 4 files changed, 32 insertions(+) diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index ae4bac00940fa..f6af664a4e9b5 100644 --- a/extmod/btstack/modbluetooth_btstack.c +++ b/extmod/btstack/modbluetooth_btstack.c @@ -1167,6 +1167,14 @@ int mp_bluetooth_gap_disconnect(uint16_t conn_handle) { return 0; } +#if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING +int mp_bluetooth_gap_pair(uint16_t conn_handle) { + DEBUG_printf("mp_bluetooth_gap_pair: conn_handle=%d\n", conn_handle); + sm_request_pairing(conn_handle); + return 0; +} +#endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE STATIC btstack_timer_source_t scan_duration_timeout; diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 06e340c42d20a..0d33130db7430 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -682,6 +682,15 @@ STATIC mp_obj_t bluetooth_ble_gap_disconnect(mp_obj_t self_in, mp_obj_t conn_han } STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gap_disconnect_obj, bluetooth_ble_gap_disconnect); +#if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING +STATIC mp_obj_t bluetooth_ble_gap_pair(mp_obj_t self_in, mp_obj_t conn_handle_in) { + (void)self_in; + uint16_t conn_handle = mp_obj_get_int(conn_handle_in); + return bluetooth_handle_errno(mp_bluetooth_gap_pair(conn_handle)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gap_pair_obj, bluetooth_ble_gap_pair); +#endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING + // ---------------------------------------------------------------------------- // Bluetooth object: GATTS (Peripheral/Advertiser role) // ---------------------------------------------------------------------------- @@ -883,6 +892,9 @@ STATIC const mp_rom_map_elem_t bluetooth_ble_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_gap_scan), MP_ROM_PTR(&bluetooth_ble_gap_scan_obj) }, #endif { MP_ROM_QSTR(MP_QSTR_gap_disconnect), MP_ROM_PTR(&bluetooth_ble_gap_disconnect_obj) }, + #if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING + { MP_ROM_QSTR(MP_QSTR_gap_pair), MP_ROM_PTR(&bluetooth_ble_gap_pair_obj) }, + #endif // GATT Server (i.e. peripheral/advertiser role) { MP_ROM_QSTR(MP_QSTR_gatts_register_services), MP_ROM_PTR(&bluetooth_ble_gatts_register_services_obj) }, { MP_ROM_QSTR(MP_QSTR_gatts_read), MP_ROM_PTR(&bluetooth_ble_gatts_read_obj) }, diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 977453becd486..48e75d432efa4 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -319,6 +319,11 @@ int mp_bluetooth_gap_disconnect(uint16_t conn_handle); int mp_bluetooth_get_preferred_mtu(void); int mp_bluetooth_set_preferred_mtu(uint16_t mtu); +#if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING +// Initiate pairing on the specified connection. +int mp_bluetooth_gap_pair(uint16_t conn_handle); +#endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE // Start a discovery (scan). Set duration to zero to run continuously. int mp_bluetooth_gap_scan_start(int32_t duration_ms, int32_t interval_us, int32_t window_us, bool active_scan); diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 852b9eac021c2..b1f13ee98b2b0 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -860,6 +860,13 @@ int mp_bluetooth_set_preferred_mtu(uint16_t mtu) { return 0; } +#if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING +int mp_bluetooth_gap_pair(uint16_t conn_handle) { + DEBUG_printf("mp_bluetooth_gap_pair: conn_handle=%d\n", conn_handle); + return ble_hs_err_to_errno(ble_gap_security_initiate(conn_handle)); +} +#endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE STATIC void gattc_on_data_available(uint8_t event, uint16_t conn_handle, uint16_t value_handle, const struct os_mbuf *om) { From fff634e03134b8f1724759f477da61b698c9cdf4 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Wed, 25 Nov 2020 00:10:17 +1100 Subject: [PATCH 117/179] docs/library/ubluetooth.rst: Add gap_pair() docs. Signed-off-by: Jim Mussared --- docs/library/ubluetooth.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/library/ubluetooth.rst b/docs/library/ubluetooth.rst index 29d3044ebf4d7..594d73c87824b 100644 --- a/docs/library/ubluetooth.rst +++ b/docs/library/ubluetooth.rst @@ -368,6 +368,15 @@ Central & Peripheral Roles Returns ``False`` if the connection handle wasn't connected, and ``True`` otherwise. +.. method:: BLE.gap_pair(conn_handle, /) + + Initiate pairing with the remote device. + + Before calling this, ensure that the ``io``, ``mitm``, ``le_secure``, and + ``bond`` configuration options are set (via :meth:`config`). + + On successful pairing, the ``_IRQ_ENCRYPTION_UPDATED`` event will be raised. + GATT Server ----------- From c4d08aa4e3641a1d05d35051454d4bce8ef14fcf Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Wed, 25 Nov 2020 17:28:04 +1100 Subject: [PATCH 118/179] extmod/modbluetooth: Add support for bonding (key persistence). This adds `_IRQ_GET_SECRET` and `_IRQ_SET_SECRET` events to allow the BT stack to request the Python code retrive/store/delete secret key data. The actual keys and values are opaque to Python and stack-specific. Only NimBLE is implemented (pending moving btstack to sync events). The secret store is designed to be compatible with BlueKitchen's TLV store API. Signed-off-by: Jim Mussared --- extmod/modbluetooth.c | 78 ++++++++----- extmod/modbluetooth.h | 18 ++- extmod/nimble/modbluetooth_nimble.c | 172 +++++++++++++++++++++++++++- extmod/nimble/nimble.mk | 2 +- 4 files changed, 240 insertions(+), 30 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 0d33130db7430..a69e8418a761d 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -1081,14 +1081,15 @@ STATIC mp_obj_t invoke_irq_handler(uint16_t event, const uint8_t *addr, const int8_t *i8, size_t n_i8, const mp_obj_bluetooth_uuid_t *uuid, - const uint8_t *data, size_t data_len) { + const uint8_t **data, size_t *data_len, size_t n_data) { mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); if (o->irq_handler == mp_const_none) { return mp_const_none; } mp_obj_array_t mv_addr; - mp_obj_array_t mv_data; + mp_obj_array_t mv_data[2]; + assert(n_data <= 2); mp_obj_tuple_t *data_tuple = mp_local_alloc(sizeof(mp_obj_tuple_t) + sizeof(mp_obj_t) * MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN); data_tuple->base.type = &mp_type_tuple; @@ -1112,9 +1113,13 @@ STATIC mp_obj_t invoke_irq_handler(uint16_t event, data_tuple->items[data_tuple->len++] = MP_OBJ_FROM_PTR(uuid); } #endif - if (data) { - mp_obj_memoryview_init(&mv_data, 'B', 0, data_len, (void *)data); - data_tuple->items[data_tuple->len++] = MP_OBJ_FROM_PTR(&mv_data); + for (size_t i = 0; i < n_data; ++i) { + if (data[i]) { + mp_obj_memoryview_init(&mv_data[i], 'B', 0, data_len[i], (void *)data[i]); + data_tuple->items[data_tuple->len++] = MP_OBJ_FROM_PTR(&mv_data[i]); + } else { + data_tuple->items[data_tuple->len++] = mp_const_none; + } } assert(data_tuple->len <= MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN); @@ -1131,36 +1136,57 @@ STATIC mp_obj_t invoke_irq_handler(uint16_t event, #define NULL_I8 NULL #define NULL_UUID NULL #define NULL_DATA NULL +#define NULL_DATA_LEN NULL void mp_bluetooth_gap_on_connected_disconnected(uint8_t event, uint16_t conn_handle, uint8_t addr_type, const uint8_t *addr) { - invoke_irq_handler(event, &conn_handle, 1, &addr_type, 1, addr, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); + invoke_irq_handler(event, &conn_handle, 1, &addr_type, 1, addr, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } void mp_bluetooth_gap_on_connection_update(uint16_t conn_handle, uint16_t conn_interval, uint16_t conn_latency, uint16_t supervision_timeout, uint16_t status) { uint16_t args[] = {conn_handle, conn_interval, conn_latency, supervision_timeout, status}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_CONNECTION_UPDATE, args, 5, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); + invoke_irq_handler(MP_BLUETOOTH_IRQ_CONNECTION_UPDATE, args, 5, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } #if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING void mp_bluetooth_gatts_on_encryption_update(uint16_t conn_handle, bool encrypted, bool authenticated, bool bonded, uint8_t key_size) { uint8_t args[] = {encrypted, authenticated, bonded, key_size}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_ENCRYPTION_UPDATE, &conn_handle, 1, args, 4, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); + invoke_irq_handler(MP_BLUETOOTH_IRQ_ENCRYPTION_UPDATE, &conn_handle, 1, args, 4, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); +} + +bool mp_bluetooth_gap_on_get_secret(uint8_t type, uint8_t index, const uint8_t *key, size_t key_len, const uint8_t **value, size_t *value_len) { + uint8_t args[] = {type, index}; + mp_obj_t result = invoke_irq_handler(MP_BLUETOOTH_IRQ_GET_SECRET, NULL_U16, 0, args, 2, NULL_ADDR, NULL_I8, 0, NULL_UUID, &key, &key_len, 1); + if (result == mp_const_none) { + return false; + } + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(result, &bufinfo, MP_BUFFER_READ); + *value = bufinfo.buf; + *value_len = bufinfo.len; + return true; +} + +bool mp_bluetooth_gap_on_set_secret(uint8_t type, const uint8_t *key, size_t key_len, const uint8_t *value, size_t value_len) { + const uint8_t *data[] = {key, value}; + size_t data_len[] = {key_len, value_len}; + mp_obj_t result = invoke_irq_handler(MP_BLUETOOTH_IRQ_SET_SECRET, NULL_U16, 0, &type, 1, NULL_ADDR, NULL_I8, 0, NULL_UUID, data, data_len, 2); + return mp_obj_is_true(result); } #endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle) { uint16_t args[] = {conn_handle, value_handle}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_WRITE, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); + invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_WRITE, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } void mp_bluetooth_gatts_on_indicate_complete(uint16_t conn_handle, uint16_t value_handle, uint8_t status) { uint16_t args[] = {conn_handle, value_handle}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_INDICATE_DONE, args, 2, &status, 1, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); + invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_INDICATE_DONE, args, 2, &status, 1, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } mp_int_t mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle) { uint16_t args[] = {conn_handle, value_handle}; - mp_obj_t result = invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST, args, 2, NULL, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); + mp_obj_t result = invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST, args, 2, NULL, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); // Return non-zero from IRQ handler to fail the read. mp_int_t ret = 0; mp_obj_get_int_maybe(result, &ret); @@ -1169,13 +1195,13 @@ mp_int_t mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value void mp_bluetooth_gatts_on_mtu_exchanged(uint16_t conn_handle, uint16_t value) { uint16_t args[] = {conn_handle, value}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_MTU_EXCHANGED, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); + invoke_irq_handler(MP_BLUETOOTH_IRQ_MTU_EXCHANGED, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } #if MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS mp_int_t mp_bluetooth_gattc_on_l2cap_accept(uint16_t conn_handle, uint16_t cid, uint16_t psm, uint16_t our_mtu, uint16_t peer_mtu) { uint16_t args[] = {conn_handle, cid, psm, our_mtu, peer_mtu}; - mp_obj_t result = invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_ACCEPT, args, 5, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); + mp_obj_t result = invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_ACCEPT, args, 5, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); // Return non-zero from IRQ handler to fail the accept. mp_int_t ret = 0; mp_obj_get_int_maybe(result, &ret); @@ -1184,58 +1210,58 @@ mp_int_t mp_bluetooth_gattc_on_l2cap_accept(uint16_t conn_handle, uint16_t cid, void mp_bluetooth_gattc_on_l2cap_connect(uint16_t conn_handle, uint16_t cid, uint16_t psm, uint16_t our_mtu, uint16_t peer_mtu) { uint16_t args[] = {conn_handle, cid, psm, our_mtu, peer_mtu}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_CONNECT, args, 5, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); + invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_CONNECT, args, 5, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } void mp_bluetooth_gattc_on_l2cap_disconnect(uint16_t conn_handle, uint16_t cid, uint16_t psm, uint16_t status) { uint16_t args[] = {conn_handle, cid, psm, status}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_DISCONNECT, args, 4, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); + invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_DISCONNECT, args, 4, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } void mp_bluetooth_gattc_on_l2cap_send_ready(uint16_t conn_handle, uint16_t cid, uint8_t status) { uint16_t args[] = {conn_handle, cid}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_SEND_READY, args, 2, &status, 1, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); + invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_SEND_READY, args, 2, &status, 1, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } void mp_bluetooth_gattc_on_l2cap_recv(uint16_t conn_handle, uint16_t cid) { uint16_t args[] = {conn_handle, cid}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_RECV, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); + invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_RECV, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } #endif // MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE void mp_bluetooth_gap_on_scan_complete(void) { - invoke_irq_handler(MP_BLUETOOTH_IRQ_SCAN_DONE, NULL_U16, 0, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); + invoke_irq_handler(MP_BLUETOOTH_IRQ_SCAN_DONE, NULL_U16, 0, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } void mp_bluetooth_gap_on_scan_result(uint8_t addr_type, const uint8_t *addr, uint8_t adv_type, const int8_t rssi, const uint8_t *data, size_t data_len) { int8_t args[] = {adv_type, rssi}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_SCAN_RESULT, NULL_U16, 0, &addr_type, 1, addr, args, 2, NULL_UUID, data, data_len); + invoke_irq_handler(MP_BLUETOOTH_IRQ_SCAN_RESULT, NULL_U16, 0, &addr_type, 1, addr, args, 2, NULL_UUID, &data, &data_len, 1); } void mp_bluetooth_gattc_on_primary_service_result(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle, mp_obj_bluetooth_uuid_t *service_uuid) { uint16_t args[] = {conn_handle, start_handle, end_handle}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT, args, 3, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, service_uuid, NULL_DATA, 0); + invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT, args, 3, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, service_uuid, NULL_DATA, NULL_DATA_LEN, 0); } void mp_bluetooth_gattc_on_characteristic_result(uint16_t conn_handle, uint16_t def_handle, uint16_t value_handle, uint8_t properties, mp_obj_bluetooth_uuid_t *characteristic_uuid) { uint16_t args[] = {conn_handle, def_handle, value_handle}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT, args, 3, &properties, 1, NULL_ADDR, NULL_I8, 0, characteristic_uuid, NULL_DATA, 0); + invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT, args, 3, &properties, 1, NULL_ADDR, NULL_I8, 0, characteristic_uuid, NULL_DATA, NULL_DATA_LEN, 0); } void mp_bluetooth_gattc_on_descriptor_result(uint16_t conn_handle, uint16_t handle, mp_obj_bluetooth_uuid_t *descriptor_uuid) { uint16_t args[] = {conn_handle, handle}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, descriptor_uuid, NULL_DATA, 0); + invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, descriptor_uuid, NULL_DATA, NULL_DATA_LEN, 0); } void mp_bluetooth_gattc_on_discover_complete(uint8_t event, uint16_t conn_handle, uint16_t status) { uint16_t args[] = {conn_handle, status}; - invoke_irq_handler(event, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); + invoke_irq_handler(event, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } void mp_bluetooth_gattc_on_data_available(uint8_t event, uint16_t conn_handle, uint16_t value_handle, const uint8_t **data, uint16_t *data_len, size_t num) { const uint8_t *combined_data; - uint16_t total_len; + size_t total_len; if (num > 1) { // Fragmented buffer, need to combine into a new heap-allocated buffer @@ -1258,7 +1284,7 @@ void mp_bluetooth_gattc_on_data_available(uint8_t event, uint16_t conn_handle, u } uint16_t args[] = {conn_handle, value_handle}; - invoke_irq_handler(event, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, combined_data, total_len); + invoke_irq_handler(event, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, &combined_data, &total_len, 1); if (num > 1) { m_del(uint8_t, (uint8_t *)combined_data, total_len); @@ -1267,7 +1293,7 @@ void mp_bluetooth_gattc_on_data_available(uint8_t event, uint16_t conn_handle, u void mp_bluetooth_gattc_on_read_write_status(uint8_t event, uint16_t conn_handle, uint16_t value_handle, uint16_t status) { uint16_t args[] = {conn_handle, value_handle, status}; - invoke_irq_handler(event, args, 3, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, 0); + invoke_irq_handler(event, args, 3, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 48e75d432efa4..d79b7f56b9828 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -146,6 +146,8 @@ #define MP_BLUETOOTH_IRQ_L2CAP_SEND_READY (26) #define MP_BLUETOOTH_IRQ_CONNECTION_UPDATE (27) #define MP_BLUETOOTH_IRQ_ENCRYPTION_UPDATE (28) +#define MP_BLUETOOTH_IRQ_GET_SECRET (29) +#define MP_BLUETOOTH_IRQ_SET_SECRET (30) #define MP_BLUETOOTH_ADDRESS_MODE_PUBLIC (0) #define MP_BLUETOOTH_ADDRESS_MODE_RANDOM (1) @@ -159,6 +161,12 @@ #define MP_BLUETOOTH_IO_CAPABILITY_NO_INPUT_OUTPUT (3) #define MP_BLUETOOTH_IO_CAPABILITY_KEYBOARD_DISPLAY (4) +// These match NimBLE BLE_SM_IOACT_. +#define MP_BLUETOOTH_PASSKEY_ACTION_NONE (0) +#define MP_BLUETOOTH_PASSKEY_ACTION_INPUT (2) +#define MP_BLUETOOTH_PASSKEY_ACTION_DISPLAY (3) +#define MP_BLUETOOTH_PASSKEY_ACTION_NUMERIC_COMPARISON (4) + /* These aren't included in the module for space reasons, but can be used in your Python code if necessary. @@ -192,6 +200,8 @@ _IRQ_L2CAP_RECV = const(25) _IRQ_L2CAP_SEND_READY = const(26) _IRQ_CONNECTION_UPDATE = const(27) _IRQ_ENCRYPTION_UPDATE = const(28) +_IRQ_GET_SECRET = const(29) +_IRQ_SET_SECRET = const(30) _FLAG_BROADCAST = const(0x0001) _FLAG_READ = const(0x0002) @@ -373,7 +383,13 @@ void mp_bluetooth_gap_on_connection_update(uint16_t conn_handle, uint16_t conn_i #if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING // Call this when any connection encryption has been changed (e.g. during pairing). void mp_bluetooth_gatts_on_encryption_update(uint16_t conn_handle, bool encrypted, bool authenticated, bool bonded, uint8_t key_size); -#endif + +// Call this when you need the application to manage persistent key data. +// For get, if key is NULL, then the implementation must return the index'th matching key. Otherwise it should return a specific key. +// For set, if value is NULL, then delete. +bool mp_bluetooth_gap_on_get_secret(uint8_t type, uint8_t index, const uint8_t *key, size_t key_len, const uint8_t **value, size_t *value_len); +bool mp_bluetooth_gap_on_set_secret(uint8_t type, const uint8_t *key, size_t key_len, const uint8_t *value, size_t value_len); +#endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING // Call this when a characteristic is written to. void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle); diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index b1f13ee98b2b0..2b273c6bc7962 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -269,6 +269,7 @@ STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { switch (event->type) { case BLE_GAP_EVENT_CONNECT: + DEBUG_printf("gap_event_cb: connect: status=%d\n", event->connect.status); if (event->connect.status == 0) { // Connection established. ble_gap_conn_find(event->connect.conn_handle, &desc); @@ -282,6 +283,7 @@ STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { case BLE_GAP_EVENT_DISCONNECT: // Disconnect. + DEBUG_printf("gap_event_cb: disconnect: reason=%d\n", event->disconnect.reason); reverse_addr_byte_order(addr, event->disconnect.conn.peer_id_addr.val); mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT, event->disconnect.conn.conn_handle, event->disconnect.conn.peer_id_addr.type, addr); break; @@ -310,7 +312,6 @@ STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { case BLE_GAP_EVENT_CONN_UPDATE: { DEBUG_printf("gap_event_cb: connection update: status=%d\n", event->conn_update.status); - struct ble_gap_conn_desc desc; if (ble_gap_conn_find(event->conn_update.conn_handle, &desc) == 0) { mp_bluetooth_gap_on_connection_update(event->conn_update.conn_handle, desc.conn_itvl, desc.conn_latency, desc.supervision_timeout, event->conn_update.status == 0 ? 0 : 1); } @@ -320,7 +321,6 @@ STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { case BLE_GAP_EVENT_ENC_CHANGE: { DEBUG_printf("gap_event_cb: enc change: status=%d\n", event->enc_change.status); #if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING - struct ble_gap_conn_desc desc; if (ble_gap_conn_find(event->enc_change.conn_handle, &desc) == 0) { mp_bluetooth_gatts_on_encryption_update(event->conn_update.conn_handle, desc.sec_state.encrypted, desc.sec_state.authenticated, @@ -329,6 +329,27 @@ STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { #endif break; } + + case BLE_GAP_EVENT_REPEAT_PAIRING: { + // We recognized this peer but the peer doesn't recognize us. + DEBUG_printf("gap_event_cb: repeat pairing: conn_handle=%d\n", event->repeat_pairing.conn_handle); + + // TODO: Consider returning BLE_GAP_REPEAT_PAIRING_IGNORE (and + // possibly an API to configure this). + + // Delete the old bond. + int rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc); + if (rc == 0) { + ble_store_util_delete_peer(&desc.peer_id_addr); + } + + // Allow re-pairing. + return BLE_GAP_REPEAT_PAIRING_RETRY; + } + + default: + DEBUG_printf("gap_event_cb: unknown type %d\n", event->type); + break; } return 0; } @@ -969,6 +990,7 @@ STATIC int peripheral_gap_event_cb(struct ble_gap_event *event, void *arg) { switch (event->type) { case BLE_GAP_EVENT_CONNECT: + DEBUG_printf("peripheral_gap_event_cb: status=%d\n", event->connect.status); if (event->connect.status == 0) { // Connection established. ble_gap_conn_find(event->connect.conn_handle, &desc); @@ -982,6 +1004,7 @@ STATIC int peripheral_gap_event_cb(struct ble_gap_event *event, void *arg) { case BLE_GAP_EVENT_DISCONNECT: // Disconnect. + DEBUG_printf("peripheral_gap_event_cb: reason=%d\n", event->disconnect.reason); reverse_addr_byte_order(addr, event->disconnect.conn.peer_id_addr.val); mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_PERIPHERAL_DISCONNECT, event->disconnect.conn.conn_handle, event->disconnect.conn.peer_id_addr.type, addr); @@ -1020,9 +1043,12 @@ STATIC int peripheral_gap_event_cb(struct ble_gap_event *event, void *arg) { #endif break; } + default: + DEBUG_printf("peripheral_gap_event_cb: unknown type %d\n", event->type); break; } + return 0; } @@ -1514,4 +1540,146 @@ int mp_bluetooth_l2cap_recvinto(uint16_t conn_handle, uint16_t cid, uint8_t *buf #endif // MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS +#if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING + +STATIC int ble_store_ram_read(int obj_type, const union ble_store_key *key, union ble_store_value *value) { + DEBUG_printf("ble_store_ram_read: %d\n", obj_type); + const uint8_t *key_data; + size_t key_data_len; + + switch (obj_type) { + case BLE_STORE_OBJ_TYPE_PEER_SEC: { + if (ble_addr_cmp(&key->sec.peer_addr, BLE_ADDR_ANY)) { + // (single) + // Find the entry for this specific peer. + assert(key->sec.idx == 0); + assert(!key->sec.ediv_rand_present); + key_data = (const uint8_t *)&key->sec.peer_addr; + key_data_len = sizeof(ble_addr_t); + } else { + // (with index) + // Iterate all known peers. + assert(!key->sec.ediv_rand_present); + key_data = NULL; + key_data_len = 0; + } + break; + } + case BLE_STORE_OBJ_TYPE_OUR_SEC: { + // + // Find our secret for this remote device, matching this ediv/rand key. + assert(ble_addr_cmp(&key->sec.peer_addr, BLE_ADDR_ANY)); // Must have address. + assert(key->sec.idx == 0); + assert(key->sec.ediv_rand_present); + key_data = (const uint8_t *)&key->sec.peer_addr; + key_data_len = sizeof(ble_addr_t); + break; + } + case BLE_STORE_OBJ_TYPE_CCCD: { + // TODO: Implement CCCD persistence. + DEBUG_printf("ble_store_ram_read: CCCD not supported.\n"); + return -1; + } + default: + return BLE_HS_ENOTSUP; + } + + const uint8_t *value_data; + size_t value_data_len; + if (!mp_bluetooth_gap_on_get_secret(obj_type, key->sec.idx, key_data, key_data_len, &value_data, &value_data_len)) { + DEBUG_printf("ble_store_ram_read: Key not found: type=%d, index=%u, key=0x%p, len=" UINT_FMT "\n", obj_type, key->sec.idx, key_data, key_data_len); + return BLE_HS_ENOENT; + } + + if (value_data_len != sizeof(struct ble_store_value_sec)) { + DEBUG_printf("ble_store_ram_read: Invalid key data: actual=" UINT_FMT " expected=" UINT_FMT "\n", value_data_len, sizeof(struct ble_store_value_sec)); + return BLE_HS_ENOENT; + } + + memcpy((uint8_t *)&value->sec, value_data, sizeof(struct ble_store_value_sec)); + + DEBUG_printf("ble_store_ram_read: found secret\n"); + + if (obj_type == BLE_STORE_OBJ_TYPE_OUR_SEC) { + // TODO: Verify ediv_rand matches. + } + + return 0; +} + +STATIC int ble_store_ram_write(int obj_type, const union ble_store_value *val) { + DEBUG_printf("ble_store_ram_write: %d\n", obj_type); + switch (obj_type) { + case BLE_STORE_OBJ_TYPE_PEER_SEC: + case BLE_STORE_OBJ_TYPE_OUR_SEC: { + // + + struct ble_store_key_sec key_sec; + const struct ble_store_value_sec *value_sec = &val->sec; + ble_store_key_from_value_sec(&key_sec, value_sec); + + assert(ble_addr_cmp(&key_sec.peer_addr, BLE_ADDR_ANY)); // Must have address. + assert(key_sec.ediv_rand_present); + + if (!mp_bluetooth_gap_on_set_secret(obj_type, (const uint8_t *)&key_sec.peer_addr, sizeof(ble_addr_t), (const uint8_t *)value_sec, sizeof(struct ble_store_value_sec))) { + DEBUG_printf("Failed to write key: type=%d\n", obj_type); + return BLE_HS_ESTORE_CAP; + } + + DEBUG_printf("ble_store_ram_write: wrote secret\n"); + + return 0; + } + case BLE_STORE_OBJ_TYPE_CCCD: { + // TODO: Implement CCCD persistence. + DEBUG_printf("ble_store_ram_write: CCCD not supported.\n"); + // Just pretend we wrote it. + return 0; + } + default: + return BLE_HS_ENOTSUP; + } +} + +STATIC int ble_store_ram_delete(int obj_type, const union ble_store_key *key) { + DEBUG_printf("ble_store_ram_delete: %d\n", obj_type); + switch (obj_type) { + case BLE_STORE_OBJ_TYPE_PEER_SEC: + case BLE_STORE_OBJ_TYPE_OUR_SEC: { + // + + assert(ble_addr_cmp(&key->sec.peer_addr, BLE_ADDR_ANY)); // Must have address. + // ediv_rand is optional (will not be present for delete). + + if (!mp_bluetooth_gap_on_set_secret(obj_type, (const uint8_t *)&key->sec.peer_addr, sizeof(ble_addr_t), NULL, 0)) { + DEBUG_printf("Failed to delete key: type=%d\n", obj_type); + return BLE_HS_ENOENT; + } + + DEBUG_printf("ble_store_ram_delete: deleted secret\n"); + + return 0; + } + case BLE_STORE_OBJ_TYPE_CCCD: { + // TODO: Implement CCCD persistence. + DEBUG_printf("ble_store_ram_delete: CCCD not supported.\n"); + // Just pretend it wasn't there. + return BLE_HS_ENOENT; + } + default: + return BLE_HS_ENOTSUP; + } +} + +// nimble_port_init always calls ble_store_ram_init. We provide this alternative +// implementation rather than the one in nimble/store/ram/src/ble_store_ram.c. +// TODO: Consider re-implementing nimble_port_init instead. +void ble_store_ram_init(void) { + ble_hs_cfg.store_read_cb = ble_store_ram_read; + ble_hs_cfg.store_write_cb = ble_store_ram_write; + ble_hs_cfg.store_delete_cb = ble_store_ram_delete; +} + +#endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING + #endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE diff --git a/extmod/nimble/nimble.mk b/extmod/nimble/nimble.mk index ba094f16f871a..806630074eb87 100644 --- a/extmod/nimble/nimble.mk +++ b/extmod/nimble/nimble.mk @@ -83,7 +83,6 @@ LIB_SRC_C += $(addprefix $(NIMBLE_LIB_DIR)/, \ ble_store_util.c \ ble_uuid.c \ ) \ - nimble/host/store/ram/src/ble_store_ram.c \ nimble/host/util/src/addr.c \ nimble/transport/uart/src/ble_hci_uart.c \ $(addprefix porting/nimble/src/, \ @@ -95,6 +94,7 @@ LIB_SRC_C += $(addprefix $(NIMBLE_LIB_DIR)/, \ os_msys_init.c \ ) \ ) + # nimble/host/store/ram/src/ble_store_ram.c \ EXTMOD_SRC_C += $(addprefix $(NIMBLE_EXTMOD_DIR)/, \ nimble/nimble_npl_os.c \ From b799fe142117f5c503c9c73fbe5f1cb4479d6f7b Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 27 Nov 2020 00:17:56 +1100 Subject: [PATCH 119/179] docs/library/ubluetooth.rst: Add bonding docs. Signed-off-by: Jim Mussared --- docs/library/ubluetooth.rst | 51 +++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/docs/library/ubluetooth.rst b/docs/library/ubluetooth.rst index 594d73c87824b..9ce400e7eb047 100644 --- a/docs/library/ubluetooth.rst +++ b/docs/library/ubluetooth.rst @@ -8,7 +8,7 @@ This module provides an interface to a Bluetooth controller on a board. Currently this supports Bluetooth Low Energy (BLE) in Central, Peripheral, Broadcaster, and Observer roles, as well as GATT Server and Client and L2CAP connection-oriented-channels. A device may operate in multiple roles -concurrently. +concurrently. Pairing (and bonding) is supported on some ports. This API is intended to match the low-level Bluetooth protocol and provide building-blocks for higher-level abstractions such as specific device types. @@ -96,7 +96,8 @@ Configuration _IO_CAPABILITY_NO_INPUT_OUTPUT = const(3) _IO_CAPABILITY_KEYBOARD_DISPLAY = const(4) - - ``'le_secure'``: Sets whether "LE Secure" pairing is required. Default is "Legacy Pairing". + - ``'le_secure'``: Sets whether "LE Secure" pairing is required. Default is + false (i.e. allow "Legacy Pairing"). Event Handling -------------- @@ -223,6 +224,16 @@ Event Handling elif event == _IRQ_ENCRYPTION_UPDATE: # The encryption state has changed (likely as a result of pairing or bonding). conn_handle, encrypted, authenticated, bonded, key_size = data + elif event == _IRQ_GET_SECRET: + # Return a stored secret. + # If key is None, return the index'th value of this sec_type. + # Otherwise return the corresponding value for this sec_type and key. + sec_type, index, key = data + return value + elif event == _IRQ_SET_SECRET: + # Save a secret to the store for this sec_type and key. + sec_type, key, value = data + return True The event codes are:: @@ -255,6 +266,8 @@ The event codes are:: _IRQ_L2CAP_SEND_READY = const(26) _IRQ_CONNECTION_UPDATE = const(27) _IRQ_ENCRYPTION_UPDATE = const(28) + _IRQ_GET_SECRET = const(29) + _IRQ_SET_SECRET = const(30) For the ``_IRQ_GATTS_READ_REQUEST`` event, the available return codes are:: @@ -368,15 +381,6 @@ Central & Peripheral Roles Returns ``False`` if the connection handle wasn't connected, and ``True`` otherwise. -.. method:: BLE.gap_pair(conn_handle, /) - - Initiate pairing with the remote device. - - Before calling this, ensure that the ``io``, ``mitm``, ``le_secure``, and - ``bond`` configuration options are set (via :meth:`config`). - - On successful pairing, the ``_IRQ_ENCRYPTION_UPDATED`` event will be raised. - GATT Server ----------- @@ -664,6 +668,31 @@ L2CAP connection-oriented-channels more channel credits and will be unable to send any more data. +Pairing and bonding +------------------- + + Pairing allows a connection to be encrypted and authenticated via exchange + of secrets (with optional MITM protection via passkey authentication). + + Bonding is the process of storing those secrets into non-volatile storage. + When bonded, a device is able to resolve a resolvable private address (RPA) + from another device based on the stored identity resolving key (IRK). + To support bonding, an application must implement the ``_IRQ_GET_SECRET`` + and ``_IRQ_SET_SECRET`` events. + + **Note:** This is currently only supported when using the NimBLE stack on + STM32 and Unix (not ESP32). + +.. method:: BLE.gap_pair(conn_handle, /) + + Initiate pairing with the remote device. + + Before calling this, ensure that the ``io``, ``mitm``, ``le_secure``, and + ``bond`` configuration options are set (via :meth:`config`). + + On successful pairing, the ``_IRQ_ENCRYPTION_UPDATE`` event will be raised. + + class UUID ---------- From 4bcbbfdb6cde6293085f4123090890c889cad50d Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Thu, 26 Nov 2020 23:48:34 +1100 Subject: [PATCH 120/179] extmod/modbluetooth: Simplify synchronous invoke_irq_handler signature. Rather than dealing with the different int types, just pass them all as a single array of mp_int_t with n_unsigned (before addr) and n_signed (after addr). Signed-off-by: Jim Mussared --- extmod/modbluetooth.c | 103 ++++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 54 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index a69e8418a761d..ba5333e70e90d 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -1076,10 +1076,8 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(bluetooth_ble_invoke_irq_obj, bluetooth_ble_inv #if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS STATIC mp_obj_t invoke_irq_handler(uint16_t event, - const uint16_t *u16, size_t n_u16, - const uint8_t *u8, size_t n_u8, + const mp_int_t *numeric, size_t n_unsigned, size_t n_signed, const uint8_t *addr, - const int8_t *i8, size_t n_i8, const mp_obj_bluetooth_uuid_t *uuid, const uint8_t **data, size_t *data_len, size_t n_data) { mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); @@ -1095,18 +1093,15 @@ STATIC mp_obj_t invoke_irq_handler(uint16_t event, data_tuple->base.type = &mp_type_tuple; data_tuple->len = 0; - for (size_t i = 0; i < n_u16; ++i) { - data_tuple->items[data_tuple->len++] = MP_OBJ_NEW_SMALL_INT(u16[i]); - } - for (size_t i = 0; i < n_u8; ++i) { - data_tuple->items[data_tuple->len++] = MP_OBJ_NEW_SMALL_INT(u8[i]); + for (size_t i = 0; i < n_unsigned; ++i) { + data_tuple->items[data_tuple->len++] = MP_OBJ_NEW_SMALL_INT(numeric[i]); } if (addr) { mp_obj_memoryview_init(&mv_addr, 'B', 0, 6, (void *)addr); data_tuple->items[data_tuple->len++] = MP_OBJ_FROM_PTR(&mv_addr); } - for (size_t i = 0; i < n_i8; ++i) { - data_tuple->items[data_tuple->len++] = MP_OBJ_NEW_SMALL_INT(i8[i]); + for (size_t i = 0; i < n_signed; ++i) { + data_tuple->items[data_tuple->len++] = MP_OBJ_NEW_SMALL_INT(numeric[i + n_unsigned]); } #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE if (uuid) { @@ -1130,32 +1125,31 @@ STATIC mp_obj_t invoke_irq_handler(uint16_t event, return result; } -#define NULL_U16 NULL -#define NULL_U8 NULL +#define NULL_NUMERIC NULL #define NULL_ADDR NULL -#define NULL_I8 NULL #define NULL_UUID NULL #define NULL_DATA NULL #define NULL_DATA_LEN NULL void mp_bluetooth_gap_on_connected_disconnected(uint8_t event, uint16_t conn_handle, uint8_t addr_type, const uint8_t *addr) { - invoke_irq_handler(event, &conn_handle, 1, &addr_type, 1, addr, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); + mp_int_t args[] = {conn_handle, addr_type}; + invoke_irq_handler(event, args, 2, 0, addr, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } void mp_bluetooth_gap_on_connection_update(uint16_t conn_handle, uint16_t conn_interval, uint16_t conn_latency, uint16_t supervision_timeout, uint16_t status) { - uint16_t args[] = {conn_handle, conn_interval, conn_latency, supervision_timeout, status}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_CONNECTION_UPDATE, args, 5, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); + mp_int_t args[] = {conn_handle, conn_interval, conn_latency, supervision_timeout, status}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_CONNECTION_UPDATE, args, 5, 0, NULL_ADDR, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } #if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING void mp_bluetooth_gatts_on_encryption_update(uint16_t conn_handle, bool encrypted, bool authenticated, bool bonded, uint8_t key_size) { - uint8_t args[] = {encrypted, authenticated, bonded, key_size}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_ENCRYPTION_UPDATE, &conn_handle, 1, args, 4, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); + mp_int_t args[] = {conn_handle, encrypted, authenticated, bonded, key_size}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_ENCRYPTION_UPDATE, args, 5, 0, NULL_ADDR, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } bool mp_bluetooth_gap_on_get_secret(uint8_t type, uint8_t index, const uint8_t *key, size_t key_len, const uint8_t **value, size_t *value_len) { - uint8_t args[] = {type, index}; - mp_obj_t result = invoke_irq_handler(MP_BLUETOOTH_IRQ_GET_SECRET, NULL_U16, 0, args, 2, NULL_ADDR, NULL_I8, 0, NULL_UUID, &key, &key_len, 1); + mp_int_t args[] = {type, index}; + mp_obj_t result = invoke_irq_handler(MP_BLUETOOTH_IRQ_GET_SECRET, args, 2, 0, NULL_ADDR, NULL_UUID, &key, &key_len, 1); if (result == mp_const_none) { return false; } @@ -1167,26 +1161,27 @@ bool mp_bluetooth_gap_on_get_secret(uint8_t type, uint8_t index, const uint8_t * } bool mp_bluetooth_gap_on_set_secret(uint8_t type, const uint8_t *key, size_t key_len, const uint8_t *value, size_t value_len) { + mp_int_t args[] = { type }; const uint8_t *data[] = {key, value}; size_t data_len[] = {key_len, value_len}; - mp_obj_t result = invoke_irq_handler(MP_BLUETOOTH_IRQ_SET_SECRET, NULL_U16, 0, &type, 1, NULL_ADDR, NULL_I8, 0, NULL_UUID, data, data_len, 2); + mp_obj_t result = invoke_irq_handler(MP_BLUETOOTH_IRQ_SET_SECRET, args, 1, 0, NULL_ADDR, NULL_UUID, data, data_len, 2); return mp_obj_is_true(result); } #endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle) { - uint16_t args[] = {conn_handle, value_handle}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_WRITE, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); + mp_int_t args[] = {conn_handle, value_handle}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_WRITE, args, 2, 0, NULL_ADDR, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } void mp_bluetooth_gatts_on_indicate_complete(uint16_t conn_handle, uint16_t value_handle, uint8_t status) { - uint16_t args[] = {conn_handle, value_handle}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_INDICATE_DONE, args, 2, &status, 1, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); + mp_int_t args[] = {conn_handle, value_handle, status}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_INDICATE_DONE, args, 3, 0, NULL_ADDR, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } mp_int_t mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle) { - uint16_t args[] = {conn_handle, value_handle}; - mp_obj_t result = invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST, args, 2, NULL, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); + mp_int_t args[] = {conn_handle, value_handle}; + mp_obj_t result = invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST, args, 2, 0, NULL_ADDR, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); // Return non-zero from IRQ handler to fail the read. mp_int_t ret = 0; mp_obj_get_int_maybe(result, &ret); @@ -1194,14 +1189,14 @@ mp_int_t mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value } void mp_bluetooth_gatts_on_mtu_exchanged(uint16_t conn_handle, uint16_t value) { - uint16_t args[] = {conn_handle, value}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_MTU_EXCHANGED, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); + mp_int_t args[] = {conn_handle, value}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_MTU_EXCHANGED, args, 2, 0, NULL_ADDR, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } #if MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS mp_int_t mp_bluetooth_gattc_on_l2cap_accept(uint16_t conn_handle, uint16_t cid, uint16_t psm, uint16_t our_mtu, uint16_t peer_mtu) { - uint16_t args[] = {conn_handle, cid, psm, our_mtu, peer_mtu}; - mp_obj_t result = invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_ACCEPT, args, 5, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); + mp_int_t args[] = {conn_handle, cid, psm, our_mtu, peer_mtu}; + mp_obj_t result = invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_ACCEPT, args, 5, 0, NULL_ADDR, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); // Return non-zero from IRQ handler to fail the accept. mp_int_t ret = 0; mp_obj_get_int_maybe(result, &ret); @@ -1209,54 +1204,54 @@ mp_int_t mp_bluetooth_gattc_on_l2cap_accept(uint16_t conn_handle, uint16_t cid, } void mp_bluetooth_gattc_on_l2cap_connect(uint16_t conn_handle, uint16_t cid, uint16_t psm, uint16_t our_mtu, uint16_t peer_mtu) { - uint16_t args[] = {conn_handle, cid, psm, our_mtu, peer_mtu}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_CONNECT, args, 5, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); + mp_int_t args[] = {conn_handle, cid, psm, our_mtu, peer_mtu}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_CONNECT, args, 5, 0, NULL_ADDR, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } void mp_bluetooth_gattc_on_l2cap_disconnect(uint16_t conn_handle, uint16_t cid, uint16_t psm, uint16_t status) { - uint16_t args[] = {conn_handle, cid, psm, status}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_DISCONNECT, args, 4, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); + mp_int_t args[] = {conn_handle, cid, psm, status}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_DISCONNECT, args, 4, 0, NULL_ADDR, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } void mp_bluetooth_gattc_on_l2cap_send_ready(uint16_t conn_handle, uint16_t cid, uint8_t status) { - uint16_t args[] = {conn_handle, cid}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_SEND_READY, args, 2, &status, 1, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); + mp_int_t args[] = {conn_handle, cid, status}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_SEND_READY, args, 3, 0, NULL_ADDR, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } void mp_bluetooth_gattc_on_l2cap_recv(uint16_t conn_handle, uint16_t cid) { - uint16_t args[] = {conn_handle, cid}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_RECV, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); + mp_int_t args[] = {conn_handle, cid}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_RECV, args, 2, 0, NULL_ADDR, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } #endif // MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE void mp_bluetooth_gap_on_scan_complete(void) { - invoke_irq_handler(MP_BLUETOOTH_IRQ_SCAN_DONE, NULL_U16, 0, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); + invoke_irq_handler(MP_BLUETOOTH_IRQ_SCAN_DONE, NULL_NUMERIC, 0, 0, NULL_ADDR, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } void mp_bluetooth_gap_on_scan_result(uint8_t addr_type, const uint8_t *addr, uint8_t adv_type, const int8_t rssi, const uint8_t *data, size_t data_len) { - int8_t args[] = {adv_type, rssi}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_SCAN_RESULT, NULL_U16, 0, &addr_type, 1, addr, args, 2, NULL_UUID, &data, &data_len, 1); + mp_int_t args[] = {addr_type, adv_type, rssi}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_SCAN_RESULT, args, 1, 2, addr, NULL_UUID, &data, &data_len, 1); } void mp_bluetooth_gattc_on_primary_service_result(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle, mp_obj_bluetooth_uuid_t *service_uuid) { - uint16_t args[] = {conn_handle, start_handle, end_handle}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT, args, 3, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, service_uuid, NULL_DATA, NULL_DATA_LEN, 0); + mp_int_t args[] = {conn_handle, start_handle, end_handle}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT, args, 3, 0, NULL_ADDR, service_uuid, NULL_DATA, NULL_DATA_LEN, 0); } void mp_bluetooth_gattc_on_characteristic_result(uint16_t conn_handle, uint16_t def_handle, uint16_t value_handle, uint8_t properties, mp_obj_bluetooth_uuid_t *characteristic_uuid) { - uint16_t args[] = {conn_handle, def_handle, value_handle}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT, args, 3, &properties, 1, NULL_ADDR, NULL_I8, 0, characteristic_uuid, NULL_DATA, NULL_DATA_LEN, 0); + mp_int_t args[] = {conn_handle, def_handle, value_handle, properties}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT, args, 4, 0, NULL_ADDR, characteristic_uuid, NULL_DATA, NULL_DATA_LEN, 0); } void mp_bluetooth_gattc_on_descriptor_result(uint16_t conn_handle, uint16_t handle, mp_obj_bluetooth_uuid_t *descriptor_uuid) { - uint16_t args[] = {conn_handle, handle}; - invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, descriptor_uuid, NULL_DATA, NULL_DATA_LEN, 0); + mp_int_t args[] = {conn_handle, handle}; + invoke_irq_handler(MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT, args, 2, 0, NULL_ADDR, descriptor_uuid, NULL_DATA, NULL_DATA_LEN, 0); } void mp_bluetooth_gattc_on_discover_complete(uint8_t event, uint16_t conn_handle, uint16_t status) { - uint16_t args[] = {conn_handle, status}; - invoke_irq_handler(event, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); + mp_int_t args[] = {conn_handle, status}; + invoke_irq_handler(event, args, 2, 0, NULL_ADDR, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } void mp_bluetooth_gattc_on_data_available(uint8_t event, uint16_t conn_handle, uint16_t value_handle, const uint8_t **data, uint16_t *data_len, size_t num) { @@ -1283,8 +1278,8 @@ void mp_bluetooth_gattc_on_data_available(uint8_t event, uint16_t conn_handle, u total_len = *data_len; } - uint16_t args[] = {conn_handle, value_handle}; - invoke_irq_handler(event, args, 2, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, &combined_data, &total_len, 1); + mp_int_t args[] = {conn_handle, value_handle}; + invoke_irq_handler(event, args, 2, 0, NULL_ADDR, NULL_UUID, &combined_data, &total_len, 1); if (num > 1) { m_del(uint8_t, (uint8_t *)combined_data, total_len); @@ -1292,8 +1287,8 @@ void mp_bluetooth_gattc_on_data_available(uint8_t event, uint16_t conn_handle, u } void mp_bluetooth_gattc_on_read_write_status(uint8_t event, uint16_t conn_handle, uint16_t value_handle, uint16_t status) { - uint16_t args[] = {conn_handle, value_handle, status}; - invoke_irq_handler(event, args, 3, NULL_U8, 0, NULL_ADDR, NULL_I8, 0, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); + mp_int_t args[] = {conn_handle, value_handle, status}; + invoke_irq_handler(event, args, 3, 0, NULL_ADDR, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); } #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE From e4f27cbee767d24f8e05678a484a584ec6fbe974 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Thu, 26 Nov 2020 23:49:45 +1100 Subject: [PATCH 121/179] extmod/modbluetooth: Add support for passkey authentication. Signed-off-by: Jim Mussared --- extmod/modbluetooth.c | 14 +++++++++++ extmod/modbluetooth.h | 20 ++++++++++++++++ extmod/nimble/modbluetooth_nimble.c | 37 +++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index ba5333e70e90d..caaf872e7ddf4 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -689,6 +689,14 @@ STATIC mp_obj_t bluetooth_ble_gap_pair(mp_obj_t self_in, mp_obj_t conn_handle_in return bluetooth_handle_errno(mp_bluetooth_gap_pair(conn_handle)); } STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gap_pair_obj, bluetooth_ble_gap_pair); + +STATIC mp_obj_t bluetooth_ble_gap_passkey(size_t n_args, const mp_obj_t *args) { + uint16_t conn_handle = mp_obj_get_int(args[1]); + uint8_t action = mp_obj_get_int(args[2]); + mp_int_t passkey = mp_obj_get_int(args[3]); + return bluetooth_handle_errno(mp_bluetooth_gap_passkey(conn_handle, action, passkey)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gap_passkey_obj, 4, 4, bluetooth_ble_gap_passkey); #endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING // ---------------------------------------------------------------------------- @@ -894,6 +902,7 @@ STATIC const mp_rom_map_elem_t bluetooth_ble_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_gap_disconnect), MP_ROM_PTR(&bluetooth_ble_gap_disconnect_obj) }, #if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING { MP_ROM_QSTR(MP_QSTR_gap_pair), MP_ROM_PTR(&bluetooth_ble_gap_pair_obj) }, + { MP_ROM_QSTR(MP_QSTR_gap_passkey), MP_ROM_PTR(&bluetooth_ble_gap_passkey_obj) }, #endif // GATT Server (i.e. peripheral/advertiser role) { MP_ROM_QSTR(MP_QSTR_gatts_register_services), MP_ROM_PTR(&bluetooth_ble_gatts_register_services_obj) }, @@ -1167,6 +1176,11 @@ bool mp_bluetooth_gap_on_set_secret(uint8_t type, const uint8_t *key, size_t key mp_obj_t result = invoke_irq_handler(MP_BLUETOOTH_IRQ_SET_SECRET, args, 1, 0, NULL_ADDR, NULL_UUID, data, data_len, 2); return mp_obj_is_true(result); } + +void mp_bluetooth_gap_on_passkey_action(uint16_t conn_handle, uint8_t action, mp_int_t passkey) { + mp_int_t args[] = { conn_handle, action, passkey }; + invoke_irq_handler(MP_BLUETOOTH_IRQ_PASSKEY_ACTION, args, 2, 1, NULL_ADDR, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0); +} #endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle) { diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index d79b7f56b9828..f14033de302e2 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -148,6 +148,7 @@ #define MP_BLUETOOTH_IRQ_ENCRYPTION_UPDATE (28) #define MP_BLUETOOTH_IRQ_GET_SECRET (29) #define MP_BLUETOOTH_IRQ_SET_SECRET (30) +#define MP_BLUETOOTH_IRQ_PASSKEY_ACTION (31) #define MP_BLUETOOTH_ADDRESS_MODE_PUBLIC (0) #define MP_BLUETOOTH_ADDRESS_MODE_RANDOM (1) @@ -167,6 +168,12 @@ #define MP_BLUETOOTH_PASSKEY_ACTION_DISPLAY (3) #define MP_BLUETOOTH_PASSKEY_ACTION_NUMERIC_COMPARISON (4) +// These match NimBLE BLE_SM_IOACT_. +#define MP_BLUETOOTH_PASSKEY_ACTION_NONE (0) +#define MP_BLUETOOTH_PASSKEY_ACTION_INPUT (2) +#define MP_BLUETOOTH_PASSKEY_ACTION_DISPLAY (3) +#define MP_BLUETOOTH_PASSKEY_ACTION_NUMERIC_COMPARISON (4) + /* These aren't included in the module for space reasons, but can be used in your Python code if necessary. @@ -202,6 +209,7 @@ _IRQ_CONNECTION_UPDATE = const(27) _IRQ_ENCRYPTION_UPDATE = const(28) _IRQ_GET_SECRET = const(29) _IRQ_SET_SECRET = const(30) +_IRQ_PASSKEY_ACTION = const(31) _FLAG_BROADCAST = const(0x0001) _FLAG_READ = const(0x0002) @@ -231,6 +239,11 @@ _IO_CAPABILITY_DISPLAY_YESNO = const(1) _IO_CAPABILITY_KEYBOARD_ONLY = const(2) _IO_CAPABILITY_NO_INPUT_OUTPUT = const(3) _IO_CAPABILITY_KEYBOARD_DISPLAY = const(4) + +_PASSKEY_ACTION_NONE = const(0) +_PASSKEY_ACTION_INPUT = const(2) +_PASSKEY_ACTION_DISPLAY = const(3) +_PASSKEY_ACTION_NUMERIC_COMPARISON = const(4) */ // bluetooth.UUID type. @@ -332,6 +345,9 @@ int mp_bluetooth_set_preferred_mtu(uint16_t mtu); #if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING // Initiate pairing on the specified connection. int mp_bluetooth_gap_pair(uint16_t conn_handle); + +// Respond to a pairing request. +int mp_bluetooth_gap_passkey(uint16_t conn_handle, uint8_t action, mp_int_t passkey); #endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE @@ -387,8 +403,12 @@ void mp_bluetooth_gatts_on_encryption_update(uint16_t conn_handle, bool encrypte // Call this when you need the application to manage persistent key data. // For get, if key is NULL, then the implementation must return the index'th matching key. Otherwise it should return a specific key. // For set, if value is NULL, then delete. +// The "type" is stack-specific, but could also be used to implement versioning. bool mp_bluetooth_gap_on_get_secret(uint8_t type, uint8_t index, const uint8_t *key, size_t key_len, const uint8_t **value, size_t *value_len); bool mp_bluetooth_gap_on_set_secret(uint8_t type, const uint8_t *key, size_t key_len, const uint8_t *value, size_t value_len); + +// Call this when a passkey verification needs to be processed. +void mp_bluetooth_gap_on_passkey_action(uint16_t conn_handle, uint8_t action, mp_int_t passkey); #endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING // Call this when a characteristic is written to. diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 2b273c6bc7962..ae727086eaeda 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -347,6 +347,16 @@ STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { return BLE_GAP_REPEAT_PAIRING_RETRY; } + case BLE_GAP_EVENT_PASSKEY_ACTION: { + DEBUG_printf("gap_event_cb: passkey action: conn_handle=%d action=%d num=" UINT_FMT "\n", event->passkey.conn_handle, event->passkey.params.action, (mp_uint_t)event->passkey.params.numcmp); + + #if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING + mp_bluetooth_gap_on_passkey_action(event->passkey.conn_handle, event->passkey.params.action, event->passkey.params.numcmp); + #endif + + return 0; + } + default: DEBUG_printf("gap_event_cb: unknown type %d\n", event->type); break; @@ -886,6 +896,33 @@ int mp_bluetooth_gap_pair(uint16_t conn_handle) { DEBUG_printf("mp_bluetooth_gap_pair: conn_handle=%d\n", conn_handle); return ble_hs_err_to_errno(ble_gap_security_initiate(conn_handle)); } + +int mp_bluetooth_gap_passkey(uint16_t conn_handle, uint8_t action, mp_int_t passkey) { + struct ble_sm_io io = {0}; + + switch (action) { + case MP_BLUETOOTH_PASSKEY_ACTION_INPUT: { + io.passkey = passkey; + break; + } + case MP_BLUETOOTH_PASSKEY_ACTION_DISPLAY: { + io.passkey = passkey; + break; + } + case MP_BLUETOOTH_PASSKEY_ACTION_NUMERIC_COMPARISON: { + io.numcmp_accept = passkey != 0; + break; + } + default: { + return MP_EINVAL; + } + } + + io.action = action; + + DEBUG_printf("mp_bluetooth_gap_passkey: injecting IO: conn_handle=%d, action=%d, passkey=" UINT_FMT ", numcmp_accept=%d\n", conn_handle, io.action, (mp_uint_t)io.passkey, io.numcmp_accept); + return ble_hs_err_to_errno(ble_sm_inject_io(conn_handle, &io)); +} #endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE From f6fd46c4024c28c827ccffd13dbe02b9ea74cfb8 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Thu, 26 Nov 2020 23:54:56 +1100 Subject: [PATCH 122/179] examples/bluetooth: Add bonding/passkey demo. Signed-off-by: Jim Mussared --- examples/bluetooth/ble_bonding_peripheral.py | 194 +++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 examples/bluetooth/ble_bonding_peripheral.py diff --git a/examples/bluetooth/ble_bonding_peripheral.py b/examples/bluetooth/ble_bonding_peripheral.py new file mode 100644 index 0000000000000..bd7596dbcb347 --- /dev/null +++ b/examples/bluetooth/ble_bonding_peripheral.py @@ -0,0 +1,194 @@ +# This example demonstrates a simple temperature sensor peripheral. +# +# The sensor's local value updates every second, and it will notify +# any connected central every 10 seconds. +# +# Work-in-progress demo of implementing bonding and passkey auth. + +import bluetooth +import random +import struct +import time +import json +import binascii +from ble_advertising import advertising_payload + +from micropython import const + +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_GATTS_INDICATE_DONE = const(20) + +_IRQ_ENCRYPTION_UPDATE = const(28) +_IRQ_PASSKEY_ACTION = const(31) + +_IRQ_GET_SECRET = const(29) +_IRQ_SET_SECRET = const(30) + +_FLAG_READ = const(0x0002) +_FLAG_NOTIFY = const(0x0010) +_FLAG_INDICATE = const(0x0020) + +_FLAG_READ_ENCRYPTED = const(0x0200) + +# org.bluetooth.service.environmental_sensing +_ENV_SENSE_UUID = bluetooth.UUID(0x181A) +# org.bluetooth.characteristic.temperature +_TEMP_CHAR = ( + bluetooth.UUID(0x2A6E), + _FLAG_READ | _FLAG_NOTIFY | _FLAG_INDICATE | _FLAG_READ_ENCRYPTED, +) +_ENV_SENSE_SERVICE = ( + _ENV_SENSE_UUID, + (_TEMP_CHAR,), +) + +# org.bluetooth.characteristic.gap.appearance.xml +_ADV_APPEARANCE_GENERIC_THERMOMETER = const(768) + +_IO_CAPABILITY_DISPLAY_ONLY = const(0) +_IO_CAPABILITY_DISPLAY_YESNO = const(1) +_IO_CAPABILITY_KEYBOARD_ONLY = const(2) +_IO_CAPABILITY_NO_INPUT_OUTPUT = const(3) +_IO_CAPABILITY_KEYBOARD_DISPLAY = const(4) + +_PASSKEY_ACTION_INPUT = const(2) +_PASSKEY_ACTION_DISP = const(3) +_PASSKEY_ACTION_NUMCMP = const(4) + + +class BLETemperature: + def __init__(self, ble, name="mpy-temp"): + self._ble = ble + self._load_secrets() + self._ble.irq(self._irq) + self._ble.config(bond=True) + self._ble.config(le_secure=True) + self._ble.config(mitm=True) + self._ble.config(io=_IO_CAPABILITY_DISPLAY_YESNO) + self._ble.active(True) + self._ble.config(addr_mode=2) + ((self._handle,),) = self._ble.gatts_register_services((_ENV_SENSE_SERVICE,)) + self._connections = set() + self._payload = advertising_payload( + name=name, services=[_ENV_SENSE_UUID], appearance=_ADV_APPEARANCE_GENERIC_THERMOMETER + ) + self._advertise() + + def _irq(self, event, data): + # Track connections so we can send notifications. + if event == _IRQ_CENTRAL_CONNECT: + conn_handle, _, _ = data + self._connections.add(conn_handle) + elif event == _IRQ_CENTRAL_DISCONNECT: + conn_handle, _, _ = data + self._connections.remove(conn_handle) + self._save_secrets() + # Start advertising again to allow a new connection. + self._advertise() + elif event == _IRQ_ENCRYPTION_UPDATE: + conn_handle, encrypted, authenticated, bonded, key_size = data + print("encryption update", conn_handle, encrypted, authenticated, bonded, key_size) + elif event == _IRQ_PASSKEY_ACTION: + conn_handle, action, passkey = data + print("passkey action", conn_handle, action, passkey) + if action == _PASSKEY_ACTION_NUMCMP: + accept = int(input("accept? ")) + self._ble.gap_passkey(conn_handle, action, accept) + elif action == _PASSKEY_ACTION_DISP: + print("displaying 123456") + self._ble.gap_passkey(conn_handle, action, 123456) + elif action == _PASSKEY_ACTION_INPUT: + print("prompting for passkey") + passkey = int(input("passkey? ")) + self._ble.gap_passkey(conn_handle, action, passkey) + else: + print("unknown action") + elif event == _IRQ_GATTS_INDICATE_DONE: + conn_handle, value_handle, status = data + elif event == _IRQ_SET_SECRET: + sec_type, key, value = data + key = sec_type, bytes(key) + value = bytes(value) if value else None + print("set secret:", key, value) + if value is None: + if key in self._secrets: + del self._secrets[key] + return True + else: + return False + else: + self._secrets[key] = value + return True + elif event == _IRQ_GET_SECRET: + sec_type, index, key = data + print("get secret:", sec_type, index, bytes(key) if key else None) + if key is None: + i = 0 + for (t, _key), value in self._secrets.items(): + if t == sec_type: + if i == index: + return value + i += 1 + return None + else: + key = sec_type, bytes(key) + return self._secrets.get(key, None) + + def set_temperature(self, temp_deg_c, notify=False, indicate=False): + # Data is sint16 in degrees Celsius with a resolution of 0.01 degrees Celsius. + # Write the local value, ready for a central to read. + self._ble.gatts_write(self._handle, struct.pack(" Date: Fri, 27 Nov 2020 00:32:26 +1100 Subject: [PATCH 123/179] docs/library/ubluetooth.rst: Add passkey docs. Signed-off-by: Jim Mussared --- docs/library/ubluetooth.rst | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/library/ubluetooth.rst b/docs/library/ubluetooth.rst index 9ce400e7eb047..3d435a7beda5a 100644 --- a/docs/library/ubluetooth.rst +++ b/docs/library/ubluetooth.rst @@ -234,6 +234,13 @@ Event Handling # Save a secret to the store for this sec_type and key. sec_type, key, value = data return True + elif event == _IRQ_PASSKEY_ACTION: + # Respond to a passkey request during pairing. + # See gap_passkey() for details. + # action will be an action that is compatible with the configured "io" config. + # passkey will be non-zero if action is "numeric comparison". + conn_handle, action, passkey = data + The event codes are:: @@ -278,6 +285,13 @@ For the ``_IRQ_GATTS_READ_REQUEST`` event, the available return codes are:: _GATTS_ERROR_INSUFFICIENT_AUTHORIZATION = const(0x08) _GATTS_ERROR_INSUFFICIENT_ENCRYPTION = const(0x0f) +For the ``_IRQ_PASSKEY_ACTION`` event, the available actions are:: + + _PASSKEY_ACTION_NONE = const(0) + _PASSKEY_ACTION_INPUT = const(2) + _PASSKEY_ACTION_DISPLAY = const(3) + _PASSKEY_ACTION_NUMERIC_COMPARISON = const(4) + In order to save space in the firmware, these constants are not included on the :mod:`ubluetooth` module. Add the ones that you need from the list above to your program. @@ -692,6 +706,22 @@ Pairing and bonding On successful pairing, the ``_IRQ_ENCRYPTION_UPDATE`` event will be raised. +.. method:: BLE.gap_passkey(conn_handle, action, passkey, /) + + Respond to a ``_IRQ_PASSKEY_ACTION`` event for the specified *conn_handle* + and *action*. + + The *passkey* is a numeric value and will depend on on the + *action* (which will depend on what I/O capability has been set): + + * When the *action* is ``_PASSKEY_ACTION_INPUT``, then the application should + prompt the user to enter the passkey that is shown on the remote device. + * When the *action* is ``_PASSKEY_ACTION_DISPLAY``, then the application should + generate a random 6-digit passkey and show it to the user. + * When the *action* is ``_PASSKEY_ACTION_NUMERIC_COMPARISON``, then the application + should show the passkey that was provided in the ``_IRQ_PASSKEY_ACTION`` event + and then respond with either ``0`` (cancel pairing), or ``1`` (accept pairing). + class UUID ---------- From d79b9c6c7cb42c69c15d1b7869a04e889d812b82 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 27 Nov 2020 17:08:22 +1100 Subject: [PATCH 124/179] extmod/nimble: Generate and persist a unique IRK. This provides a workaround for https://github.com/apache/mynewt-nimble/issues/887. Without this, all devices would share a fixed default IRK. Signed-off-by: Jim Mussared --- extmod/nimble/modbluetooth_nimble.c | 58 +++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index ae727086eaeda..613e325a953f5 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -189,6 +189,59 @@ STATIC void set_random_address(bool nrpa) { assert(rc == 0); } +#if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING +// For ble_hs_pvcy_set_our_irk +#include "nimble/host/src/ble_hs_pvcy_priv.h" +// For ble_hs_hci_util_rand +#include "nimble/host/src/ble_hs_hci_priv.h" +// For ble_hs_misc_restore_irks +#include "nimble/host/src/ble_hs_priv.h" + +// Must be distinct to BLE_STORE_OBJ_TYPE_ in ble_store.h. +#define SECRET_TYPE_OUR_IRK 10 + +STATIC int load_irk(void) { + // NimBLE unconditionally loads a fixed IRK on startup. + // See https://github.com/apache/mynewt-nimble/issues/887 + + // Dummy key to use for the store. + // Technically the secret type is enough as there will only be + // one IRK so the key doesn't matter, but a NULL (None) key means "search". + const uint8_t key[3] = {'i', 'r', 'k'}; + + int rc; + const uint8_t *irk; + size_t irk_len; + if (mp_bluetooth_gap_on_get_secret(SECRET_TYPE_OUR_IRK, 0, key, sizeof(key), &irk, &irk_len) && irk_len == 16) { + DEBUG_printf("load_irk: Applying IRK from store.\n"); + rc = ble_hs_pvcy_set_our_irk(irk); + if (rc) { + return rc; + } + } else { + DEBUG_printf("load_irk: Generating new IRK.\n"); + uint8_t rand_irk[16]; + rc = ble_hs_hci_util_rand(rand_irk, 16); + if (rc) { + return rc; + } + DEBUG_printf("load_irk: Saving new IRK.\n"); + if (!mp_bluetooth_gap_on_set_secret(SECRET_TYPE_OUR_IRK, key, sizeof(key), rand_irk, 16)) { + return BLE_HS_EINVAL; + } + DEBUG_printf("load_irk: Applying new IRK.\n"); + rc = ble_hs_pvcy_set_our_irk(rand_irk); + if (rc) { + return rc; + } + } + + // Loading an IRK will clear all peer IRKs, so reload them from the store. + rc = ble_hs_misc_restore_irks(); + return rc; +} +#endif + STATIC void sync_cb(void) { int rc; (void)rc; @@ -199,6 +252,11 @@ STATIC void sync_cb(void) { return; } + #if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING + rc = load_irk(); + assert(rc == 0); + #endif + if (has_public_address()) { nimble_address_mode = BLE_OWN_ADDR_PUBLIC; } else { From c8b055717805500494a14870a4200bbc933fe337 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 1 Dec 2020 22:00:02 +1100 Subject: [PATCH 125/179] tests/multi_bluetooth: Add multitests for BLE pairing and bonding. Signed-off-by: Damien George --- tests/multi_bluetooth/ble_gap_pair.py | 129 +++++++++++++++++ tests/multi_bluetooth/ble_gap_pair.py.exp | 17 +++ tests/multi_bluetooth/ble_gap_pair_bond.py | 134 ++++++++++++++++++ .../multi_bluetooth/ble_gap_pair_bond.py.exp | 17 +++ 4 files changed, 297 insertions(+) create mode 100644 tests/multi_bluetooth/ble_gap_pair.py create mode 100644 tests/multi_bluetooth/ble_gap_pair.py.exp create mode 100644 tests/multi_bluetooth/ble_gap_pair_bond.py create mode 100644 tests/multi_bluetooth/ble_gap_pair_bond.py.exp diff --git a/tests/multi_bluetooth/ble_gap_pair.py b/tests/multi_bluetooth/ble_gap_pair.py new file mode 100644 index 0000000000000..728513405ee79 --- /dev/null +++ b/tests/multi_bluetooth/ble_gap_pair.py @@ -0,0 +1,129 @@ +# Test BLE GAP connect/disconnect with pairing, and read an encrypted characteristic +# TODO: add gap_passkey testing + +from micropython import const +import time, machine, bluetooth + +TIMEOUT_MS = 4000 + +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_GATTS_READ_REQUEST = const(4) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) +_IRQ_GATTC_CHARACTERISTIC_DONE = const(12) +_IRQ_GATTC_READ_RESULT = const(15) +_IRQ_ENCRYPTION_UPDATE = const(28) + +_FLAG_READ = const(0x0002) +_FLAG_READ_ENCRYPTED = const(0x0200) + +SERVICE_UUID = bluetooth.UUID("A5A5A5A5-FFFF-9999-1111-5A5A5A5A5A5A") +CHAR_UUID = bluetooth.UUID("00000000-1111-2222-3333-444444444444") +CHAR = (CHAR_UUID, _FLAG_READ | _FLAG_READ_ENCRYPTED) +SERVICE = (SERVICE_UUID, (CHAR,)) + +waiting_events = {} + + +def irq(event, data): + if event == _IRQ_CENTRAL_CONNECT: + print("_IRQ_CENTRAL_CONNECT") + waiting_events[event] = data[0] + elif event == _IRQ_CENTRAL_DISCONNECT: + print("_IRQ_CENTRAL_DISCONNECT") + elif event == _IRQ_GATTS_READ_REQUEST: + print("_IRQ_GATTS_READ_REQUEST") + elif event == _IRQ_PERIPHERAL_CONNECT: + print("_IRQ_PERIPHERAL_CONNECT") + waiting_events[event] = data[0] + elif event == _IRQ_PERIPHERAL_DISCONNECT: + print("_IRQ_PERIPHERAL_DISCONNECT") + elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT: + if data[-1] == CHAR_UUID: + print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) + waiting_events[event] = data[2] + else: + return + elif event == _IRQ_GATTC_CHARACTERISTIC_DONE: + print("_IRQ_GATTC_CHARACTERISTIC_DONE") + elif event == _IRQ_GATTC_READ_RESULT: + print("_IRQ_GATTC_READ_RESULT", bytes(data[-1])) + elif event == _IRQ_ENCRYPTION_UPDATE: + print("_IRQ_ENCRYPTION_UPDATE", data[1], data[2], data[3]) + + if event not in waiting_events: + waiting_events[event] = None + + +def wait_for_event(event, timeout_ms): + t0 = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: + if event in waiting_events: + return waiting_events.pop(event) + machine.idle() + raise ValueError("Timeout waiting for {}".format(event)) + + +# Acting in peripheral role. +def instance0(): + multitest.globals(BDADDR=ble.config("mac")) + ((char_handle,),) = ble.gatts_register_services((SERVICE,)) + ble.gatts_write(char_handle, "encrypted") + print("gap_advertise") + ble.gap_advertise(20_000, b"\x02\x01\x06\x04\xffMPY") + multitest.next() + try: + # Wait for central to connect. + wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) + + # Wait for pairing event. + wait_for_event(_IRQ_ENCRYPTION_UPDATE, TIMEOUT_MS) + + # Wait for GATTS read request. + wait_for_event(_IRQ_GATTS_READ_REQUEST, TIMEOUT_MS) + + # Wait for central to disconnect. + wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS) + finally: + ble.active(0) + + +# Acting in central role. +def instance1(): + multitest.next() + try: + # Connect to peripheral. + print("gap_connect") + ble.gap_connect(*BDADDR) + conn_handle = wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) + + # Discover characteristics (before pairing, doesn't need to be encrypted). + ble.gattc_discover_characteristics(conn_handle, 1, 65535) + value_handle = wait_for_event(_IRQ_GATTC_CHARACTERISTIC_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_CHARACTERISTIC_DONE, TIMEOUT_MS) + + # Pair with the peripheral. + print("gap_pair") + ble.gap_pair(conn_handle) + + # Wait for the pairing event. + wait_for_event(_IRQ_ENCRYPTION_UPDATE, TIMEOUT_MS) + + # Read the peripheral's characteristic, should be encrypted. + print("gattc_read") + ble.gattc_read(conn_handle, value_handle) + wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) + + # Disconnect from the peripheral. + print("gap_disconnect:", ble.gap_disconnect(conn_handle)) + wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS) + finally: + ble.active(0) + + +ble = bluetooth.BLE() +ble.config(mitm=True, le_secure=True, bond=False) +ble.active(1) +ble.irq(irq) diff --git a/tests/multi_bluetooth/ble_gap_pair.py.exp b/tests/multi_bluetooth/ble_gap_pair.py.exp new file mode 100644 index 0000000000000..9efe0fb22b2b4 --- /dev/null +++ b/tests/multi_bluetooth/ble_gap_pair.py.exp @@ -0,0 +1,17 @@ +--- instance0 --- +gap_advertise +_IRQ_CENTRAL_CONNECT +_IRQ_ENCRYPTION_UPDATE 1 0 0 +_IRQ_GATTS_READ_REQUEST +_IRQ_CENTRAL_DISCONNECT +--- instance1 --- +gap_connect +_IRQ_PERIPHERAL_CONNECT +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gap_pair +_IRQ_ENCRYPTION_UPDATE 1 0 0 +gattc_read +_IRQ_GATTC_READ_RESULT b'encrypted' +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT diff --git a/tests/multi_bluetooth/ble_gap_pair_bond.py b/tests/multi_bluetooth/ble_gap_pair_bond.py new file mode 100644 index 0000000000000..a29c217887b45 --- /dev/null +++ b/tests/multi_bluetooth/ble_gap_pair_bond.py @@ -0,0 +1,134 @@ +# Test BLE GAP connect/disconnect with pairing and bonding, and read an encrypted +# characteristic +# TODO: reconnect after bonding to test that the secrets persist + +from micropython import const +import time, machine, bluetooth + +TIMEOUT_MS = 4000 + +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_GATTS_READ_REQUEST = const(4) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) +_IRQ_GATTC_CHARACTERISTIC_DONE = const(12) +_IRQ_GATTC_READ_RESULT = const(15) +_IRQ_ENCRYPTION_UPDATE = const(28) +_IRQ_SET_SECRET = const(30) + +_FLAG_READ = const(0x0002) +_FLAG_READ_ENCRYPTED = const(0x0200) + +SERVICE_UUID = bluetooth.UUID("A5A5A5A5-FFFF-9999-1111-5A5A5A5A5A5A") +CHAR_UUID = bluetooth.UUID("00000000-1111-2222-3333-444444444444") +CHAR = (CHAR_UUID, _FLAG_READ | _FLAG_READ_ENCRYPTED) +SERVICE = (SERVICE_UUID, (CHAR,)) + +waiting_events = {} +secrets = {} + + +def irq(event, data): + if event == _IRQ_CENTRAL_CONNECT: + print("_IRQ_CENTRAL_CONNECT") + waiting_events[event] = data[0] + elif event == _IRQ_CENTRAL_DISCONNECT: + print("_IRQ_CENTRAL_DISCONNECT") + elif event == _IRQ_GATTS_READ_REQUEST: + print("_IRQ_GATTS_READ_REQUEST") + elif event == _IRQ_PERIPHERAL_CONNECT: + print("_IRQ_PERIPHERAL_CONNECT") + waiting_events[event] = data[0] + elif event == _IRQ_PERIPHERAL_DISCONNECT: + print("_IRQ_PERIPHERAL_DISCONNECT") + elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT: + if data[-1] == CHAR_UUID: + print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) + waiting_events[event] = data[2] + else: + return + elif event == _IRQ_GATTC_CHARACTERISTIC_DONE: + print("_IRQ_GATTC_CHARACTERISTIC_DONE") + elif event == _IRQ_GATTC_READ_RESULT: + print("_IRQ_GATTC_READ_RESULT", bytes(data[-1])) + elif event == _IRQ_ENCRYPTION_UPDATE: + print("_IRQ_ENCRYPTION_UPDATE", data[1], data[2], data[3]) + elif event == _IRQ_SET_SECRET: + return True + + if event not in waiting_events: + waiting_events[event] = None + + +def wait_for_event(event, timeout_ms): + t0 = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: + if event in waiting_events: + return waiting_events.pop(event) + machine.idle() + raise ValueError("Timeout waiting for {}".format(event)) + + +# Acting in peripheral role. +def instance0(): + multitest.globals(BDADDR=ble.config("mac")) + ((char_handle,),) = ble.gatts_register_services((SERVICE,)) + ble.gatts_write(char_handle, "encrypted") + print("gap_advertise") + ble.gap_advertise(20_000, b"\x02\x01\x06\x04\xffMPY") + multitest.next() + try: + # Wait for central to connect. + wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) + + # Wait for pairing event. + wait_for_event(_IRQ_ENCRYPTION_UPDATE, TIMEOUT_MS) + + # Wait for GATTS read request. + wait_for_event(_IRQ_GATTS_READ_REQUEST, TIMEOUT_MS) + + # Wait for central to disconnect. + wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS) + finally: + ble.active(0) + + +# Acting in central role. +def instance1(): + multitest.next() + try: + # Connect to peripheral. + print("gap_connect") + ble.gap_connect(*BDADDR) + conn_handle = wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) + + # Discover characteristics (before pairing, doesn't need to be encrypted). + ble.gattc_discover_characteristics(conn_handle, 1, 65535) + value_handle = wait_for_event(_IRQ_GATTC_CHARACTERISTIC_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_CHARACTERISTIC_DONE, TIMEOUT_MS) + + # Pair with the peripheral. + print("gap_pair") + ble.gap_pair(conn_handle) + + # Wait for the pairing event. + wait_for_event(_IRQ_ENCRYPTION_UPDATE, TIMEOUT_MS) + + # Read the peripheral's characteristic, should be encrypted. + print("gattc_read") + ble.gattc_read(conn_handle, value_handle) + wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) + + # Disconnect from the peripheral. + print("gap_disconnect:", ble.gap_disconnect(conn_handle)) + wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS) + finally: + ble.active(0) + + +ble = bluetooth.BLE() +ble.config(mitm=True, le_secure=True, bond=True) +ble.active(1) +ble.irq(irq) diff --git a/tests/multi_bluetooth/ble_gap_pair_bond.py.exp b/tests/multi_bluetooth/ble_gap_pair_bond.py.exp new file mode 100644 index 0000000000000..271403a71a8c7 --- /dev/null +++ b/tests/multi_bluetooth/ble_gap_pair_bond.py.exp @@ -0,0 +1,17 @@ +--- instance0 --- +gap_advertise +_IRQ_CENTRAL_CONNECT +_IRQ_ENCRYPTION_UPDATE 1 0 1 +_IRQ_GATTS_READ_REQUEST +_IRQ_CENTRAL_DISCONNECT +--- instance1 --- +gap_connect +_IRQ_PERIPHERAL_CONNECT +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gap_pair +_IRQ_ENCRYPTION_UPDATE 1 0 1 +gattc_read +_IRQ_GATTC_READ_RESULT b'encrypted' +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT From 849748873cd838be0d91555b47f1447f4476175a Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Wed, 25 Nov 2020 15:15:49 +0200 Subject: [PATCH 126/179] stm32/modmachine: Add device and revision ids to machine.info(). --- ports/stm32/modmachine.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/stm32/modmachine.c b/ports/stm32/modmachine.c index 9fa00a915186f..c5f49b6ef4395 100644 --- a/ports/stm32/modmachine.c +++ b/ports/stm32/modmachine.c @@ -155,6 +155,8 @@ STATIC mp_obj_t machine_info(size_t n_args, const mp_obj_t *args) { printf("ID=%02x%02x%02x%02x:%02x%02x%02x%02x:%02x%02x%02x%02x\n", id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11]); } + printf("DEVID=0x%04x\nREVID=0x%04x\n", (unsigned int)HAL_GetDEVID(), (unsigned int)HAL_GetREVID()); + // get and print clock speeds // SYSCLK=168MHz, HCLK=168MHz, PCLK1=42MHz, PCLK2=84MHz { From ce9197eb209bacd1453ae2ef83523aa0fa1f9b45 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Fri, 27 Nov 2020 02:38:00 +0200 Subject: [PATCH 127/179] stm32/Makefile: Disable text compression in debug builds. Otherwise the flash overflows. Fixes issue #6653. --- ports/stm32/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index ced851ca38e27..c3b7bbba98c61 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -134,6 +134,8 @@ LDFLAGS += --gc-sections ifeq ($(DEBUG), 1) CFLAGS += -g -DPENDSV_DEBUG COPT = -O0 +# Disable text compression in debug builds +MICROPY_ROM_TEXT_COMPRESSION = 0 else COPT += -Os -DNDEBUG endif From e9e619fa24cca3aa6788931d6fee561436576f83 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Wed, 2 Dec 2020 23:16:10 +0200 Subject: [PATCH 128/179] stm32/powerctrl: Define RCC_SR_SFTRSTF flag for H747. --- ports/stm32/powerctrl.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/stm32/powerctrl.c b/ports/stm32/powerctrl.c index 946185fba09d3..01ad1834184b4 100644 --- a/ports/stm32/powerctrl.c +++ b/ports/stm32/powerctrl.c @@ -32,7 +32,11 @@ #if defined(STM32H7) #define RCC_SR RSR +#if defined(STM32H743xx) #define RCC_SR_SFTRSTF RCC_RSR_SFTRSTF +#elif defined(STM32H747xx) +#define RCC_SR_SFTRSTF RCC_RSR_SFT2RSTF +#endif #define RCC_SR_RMVF RCC_RSR_RMVF #else #define RCC_SR CSR From 3e5dd2dbcc87710f9f3f3bf4cd732ecdcc922b1f Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Wed, 2 Dec 2020 23:31:56 +0200 Subject: [PATCH 129/179] stm32/powerctrl: Fix STOP mode voltage scaling on H7 REV V devices. --- ports/stm32/powerctrl.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/ports/stm32/powerctrl.c b/ports/stm32/powerctrl.c index 01ad1834184b4..7d368169790d1 100644 --- a/ports/stm32/powerctrl.c +++ b/ports/stm32/powerctrl.c @@ -38,6 +38,12 @@ #define RCC_SR_SFTRSTF RCC_RSR_SFT2RSTF #endif #define RCC_SR_RMVF RCC_RSR_RMVF +// This macro returns the actual voltage scaling level factoring in the power overdrive bit. +// If the current voltage scale is VOLTAGE_SCALE1 and PWER_ODEN bit is set return VOLTAGE_SCALE0 +// otherwise the current voltage scaling (level VOS1 to VOS3) set in PWER_CSR is returned instead. +#define POWERCTRL_GET_VOLTAGE_SCALING() \ + (((PWR->CSR1 & PWR_CSR1_ACTVOS) && (SYSCFG->PWRCR & SYSCFG_PWRCR_ODEN)) ? \ + PWR_REGULATOR_VOLTAGE_SCALE0 : (PWR->CSR1 & PWR_CSR1_ACTVOS)) #else #define RCC_SR CSR #define RCC_SR_SFTRSTF RCC_CSR_SFTRSTF @@ -510,6 +516,19 @@ void powerctrl_enter_stop_mode(void) { HAL_PWREx_EnableFlashPowerDown(); #endif + #if defined(STM32H7) + // Save the current voltage scaling level to restore after exiting low power mode. + uint32_t vscaling = POWERCTRL_GET_VOLTAGE_SCALING(); + + // If the current voltage scaling level is 0, switch to level 1 before entering low power mode. + if (vscaling == PWR_REGULATOR_VOLTAGE_SCALE0) { + __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); + // Wait for PWR_FLAG_VOSRDY + while (!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) { + } + } + #endif + #if defined(STM32F7) HAL_PWR_EnterSTOPMode((PWR_CR1_LPDS | PWR_CR1_LPUDS | PWR_CR1_FPDS | PWR_CR1_UDEN), PWR_STOPENTRY_WFI); #else @@ -532,6 +551,17 @@ void powerctrl_enter_stop_mode(void) { #else + #if defined(STM32H7) + // When exiting from Stop or Standby modes, the Run mode voltage scaling is reset to + // the default VOS3 value. Restore the voltage scaling to the previous voltage scale. + if (vscaling != POWERCTRL_GET_VOLTAGE_SCALING()) { + __HAL_PWR_VOLTAGESCALING_CONFIG(vscaling); + // Wait for PWR_FLAG_VOSRDY + while (!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) { + } + } + #endif + #if !defined(STM32L4) // enable clock __HAL_RCC_HSE_CONFIG(MICROPY_HW_RCC_HSE_STATE); From 463a275bc4e99a1d37d523a120fe45976ea021fe Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Wed, 2 Dec 2020 23:33:56 +0200 Subject: [PATCH 130/179] stm32/powerctrl: On H7, re-enable disabled OSCs/PLLs on exit from STOP. This commit saves OSCs/PLLs state before STOP mode and restores them on exit. Some boards use HSI48 for USB for example, others have PLL2/3 enabled, etc. --- ports/stm32/powerctrl.c | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/ports/stm32/powerctrl.c b/ports/stm32/powerctrl.c index 7d368169790d1..c36351e311987 100644 --- a/ports/stm32/powerctrl.c +++ b/ports/stm32/powerctrl.c @@ -517,6 +517,9 @@ void powerctrl_enter_stop_mode(void) { #endif #if defined(STM32H7) + // Save RCC CR to re-enable OSCs and PLLs after wake up from low power mode. + uint32_t rcc_cr = RCC->CR; + // Save the current voltage scaling level to restore after exiting low power mode. uint32_t vscaling = POWERCTRL_GET_VOLTAGE_SCALING(); @@ -607,9 +610,39 @@ void powerctrl_enter_stop_mode(void) { #endif #if defined(STM32H7) - // Enable PLL3 for USB - RCC->CR |= RCC_CR_PLL3ON; - while (!(RCC->CR & RCC_CR_PLL3RDY)) { + // Enable HSI + if (rcc_cr & RCC_CR_HSION) { + RCC->CR |= RCC_CR_HSION; + while (!(RCC->CR & RCC_CR_HSIRDY)) { + } + } + + // Enable CSI + if (rcc_cr & RCC_CR_CSION) { + RCC->CR |= RCC_CR_CSION; + while (!(RCC->CR & RCC_CR_CSIRDY)) { + } + } + + // Enable HSI48 + if (rcc_cr & RCC_CR_HSI48ON) { + RCC->CR |= RCC_CR_HSI48ON; + while (!(RCC->CR & RCC_CR_HSI48RDY)) { + } + } + + // Enable PLL2 + if (rcc_cr & RCC_CR_PLL2ON) { + RCC->CR |= RCC_CR_PLL2ON; + while (!(RCC->CR & RCC_CR_PLL2RDY)) { + } + } + + // Enable PLL3 + if (rcc_cr & RCC_CR_PLL3ON) { + RCC->CR |= RCC_CR_PLL3ON; + while (!(RCC->CR & RCC_CR_PLL3RDY)) { + } } #endif From 8add94e94e906ecf6c46d49127a8a0590be308b8 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Wed, 2 Dec 2020 23:35:58 +0200 Subject: [PATCH 131/179] stm32/powerctrl: Disable RTC write protection before changing flags. --- ports/stm32/powerctrl.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ports/stm32/powerctrl.c b/ports/stm32/powerctrl.c index c36351e311987..ce2f6e1905c5d 100644 --- a/ports/stm32/powerctrl.c +++ b/ports/stm32/powerctrl.c @@ -689,6 +689,10 @@ void powerctrl_enter_standby_mode(void) { // save RTC interrupts uint32_t save_irq_bits = RTC->CR & CR_BITS; + // disable register write protection + RTC->WPR = 0xca; + RTC->WPR = 0x53; + // disable RTC interrupts RTC->CR &= ~CR_BITS; @@ -714,6 +718,9 @@ void powerctrl_enter_standby_mode(void) { // enable previously-enabled RTC interrupts RTC->CR |= save_irq_bits; + // enable register write protection + RTC->WPR = 0xff; + #if defined(STM32F7) // Enable the internal (eg RTC) wakeup sources // See Errata 2.2.2 "Wakeup from Standby mode when the back-up SRAM regulator is enabled" From 7b9b6d080a2d7de6db73fe4caaca29f54a1d716d Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Wed, 2 Dec 2020 23:36:47 +0200 Subject: [PATCH 132/179] stm32/powerctrl: Set H7 RTC wakeup flags. --- ports/stm32/powerctrl.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/stm32/powerctrl.c b/ports/stm32/powerctrl.c index ce2f6e1905c5d..076215ba9f287 100644 --- a/ports/stm32/powerctrl.c +++ b/ports/stm32/powerctrl.c @@ -705,7 +705,8 @@ void powerctrl_enter_standby_mode(void) { // clear global wake-up flag PWR->CR2 |= PWR_CR2_CWUPF6 | PWR_CR2_CWUPF5 | PWR_CR2_CWUPF4 | PWR_CR2_CWUPF3 | PWR_CR2_CWUPF2 | PWR_CR2_CWUPF1; #elif defined(STM32H7) - // TODO + EXTI_D1->PR1 = 0x3fffff; + PWR->WKUPCR |= PWR_WAKEUP_FLAG1 | PWR_WAKEUP_FLAG2 | PWR_WAKEUP_FLAG3 | PWR_WAKEUP_FLAG4 | PWR_WAKEUP_FLAG5 | PWR_WAKEUP_FLAG6; #elif defined(STM32L4) || defined(STM32WB) // clear all wake-up flags PWR->SCR |= PWR_SCR_CWUF5 | PWR_SCR_CWUF4 | PWR_SCR_CWUF3 | PWR_SCR_CWUF2 | PWR_SCR_CWUF1; From 7dc2f4ed384f45eb943e0b7dc39a539af7058b80 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 3 Dec 2020 19:46:07 +0200 Subject: [PATCH 133/179] stm32/powerctrl: Ensure SysTick is disabled on STOP mode entry for H7. Even though IRQs are disabled this seems to be required on H7 Rev Y, otherwise Systick interrupt triggers and the MCU leaves the stop mode immediately. --- ports/stm32/powerctrl.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ports/stm32/powerctrl.c b/ports/stm32/powerctrl.c index 076215ba9f287..bf8be647f4d76 100644 --- a/ports/stm32/powerctrl.c +++ b/ports/stm32/powerctrl.c @@ -502,6 +502,13 @@ void powerctrl_enter_stop_mode(void) { // executed until after the clocks are reconfigured uint32_t irq_state = disable_irq(); + #if defined(STM32H7) + // Disable SysTick Interrupt + // Note: This seems to be required at least on the H7 REV Y, + // otherwise the MCU will leave stop mode immediately on entry. + SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk; + #endif + #if defined(MICROPY_BOARD_ENTER_STOP) MICROPY_BOARD_ENTER_STOP #endif @@ -659,6 +666,11 @@ void powerctrl_enter_stop_mode(void) { MICROPY_BOARD_LEAVE_STOP #endif + #if defined(STM32H7) + // Enable SysTick Interrupt + SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; + #endif + // Enable IRQs now that all clocks are reconfigured enable_irq(irq_state); } From 4ce6427bd71129b1ab3b9eeaf2c1182d623cd7b9 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 3 Dec 2020 12:58:48 +1100 Subject: [PATCH 134/179] stm32/i2c: Factor I2C finding code to i2c_find_peripheral function. Signed-off-by: Damien George --- ports/stm32/i2c.c | 50 +++++++++++++++++++++++++++++++++++++++ ports/stm32/i2c.h | 2 ++ ports/stm32/machine_i2c.c | 33 +------------------------- ports/stm32/pyb_i2c.c | 33 +------------------------- 4 files changed, 54 insertions(+), 64 deletions(-) diff --git a/ports/stm32/i2c.c b/ports/stm32/i2c.c index 5981df11c5bd8..d67201b714418 100644 --- a/ports/stm32/i2c.c +++ b/ports/stm32/i2c.c @@ -26,6 +26,7 @@ #include "py/mperrno.h" #include "py/mphal.h" +#include "py/runtime.h" #include "i2c.h" #if MICROPY_HW_ENABLE_HW_I2C @@ -487,4 +488,53 @@ int i2c_writeto(i2c_t *i2c, uint16_t addr, const uint8_t *src, size_t len, bool #endif +STATIC const uint8_t i2c_available = + 0 + #if defined(MICROPY_HW_I2C1_SCL) + | 1 << 1 + #endif + #if defined(MICROPY_HW_I2C2_SCL) + | 1 << 2 + #endif + #if defined(MICROPY_HW_I2C3_SCL) + | 1 << 3 + #endif + #if defined(MICROPY_HW_I2C4_SCL) + | 1 << 4 + #endif +; + +int i2c_find_peripheral(mp_obj_t id) { + int i2c_id = 0; + if (mp_obj_is_str(id)) { + const char *port = mp_obj_str_get_str(id); + if (0) { + #ifdef MICROPY_HW_I2C1_NAME + } else if (strcmp(port, MICROPY_HW_I2C1_NAME) == 0) { + i2c_id = 1; + #endif + #ifdef MICROPY_HW_I2C2_NAME + } else if (strcmp(port, MICROPY_HW_I2C2_NAME) == 0) { + i2c_id = 2; + #endif + #ifdef MICROPY_HW_I2C3_NAME + } else if (strcmp(port, MICROPY_HW_I2C3_NAME) == 0) { + i2c_id = 3; + #endif + #ifdef MICROPY_HW_I2C4_NAME + } else if (strcmp(port, MICROPY_HW_I2C4_NAME) == 0) { + i2c_id = 4; + #endif + } else { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%s) doesn't exist"), port); + } + } else { + i2c_id = mp_obj_get_int(id); + if (i2c_id < 1 || i2c_id >= 8 * sizeof(i2c_available) || !(i2c_available & (1 << i2c_id))) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't exist"), i2c_id); + } + } + return i2c_id; +} + #endif // MICROPY_HW_ENABLE_HW_I2C diff --git a/ports/stm32/i2c.h b/ports/stm32/i2c.h index 4846f1cf3ab52..d494ad66d57f5 100644 --- a/ports/stm32/i2c.h +++ b/ports/stm32/i2c.h @@ -62,4 +62,6 @@ int i2c_write(i2c_t *i2c, const uint8_t *src, size_t len, size_t next_len); int i2c_readfrom(i2c_t *i2c, uint16_t addr, uint8_t *dest, size_t len, bool stop); int i2c_writeto(i2c_t *i2c, uint16_t addr, const uint8_t *src, size_t len, bool stop); +int i2c_find_peripheral(mp_obj_t id); + #endif // MICROPY_INCLUDED_STM32_I2C_H diff --git a/ports/stm32/machine_i2c.c b/ports/stm32/machine_i2c.c index cfa00b86d830b..41e65cf05cb37 100644 --- a/ports/stm32/machine_i2c.c +++ b/ports/stm32/machine_i2c.c @@ -210,39 +210,8 @@ mp_obj_t machine_hard_i2c_make_new(const mp_obj_type_t *type, size_t n_args, siz 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); - // work out i2c bus - int i2c_id = 0; - if (mp_obj_is_str(args[ARG_id].u_obj)) { - const char *port = mp_obj_str_get_str(args[ARG_id].u_obj); - if (0) { - #ifdef MICROPY_HW_I2C1_NAME - } else if (strcmp(port, MICROPY_HW_I2C1_NAME) == 0) { - i2c_id = 1; - #endif - #ifdef MICROPY_HW_I2C2_NAME - } else if (strcmp(port, MICROPY_HW_I2C2_NAME) == 0) { - i2c_id = 2; - #endif - #ifdef MICROPY_HW_I2C3_NAME - } else if (strcmp(port, MICROPY_HW_I2C3_NAME) == 0) { - i2c_id = 3; - #endif - #ifdef MICROPY_HW_I2C4_NAME - } else if (strcmp(port, MICROPY_HW_I2C4_NAME) == 0) { - i2c_id = 4; - #endif - } else { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%s) doesn't exist"), port); - } - } else { - i2c_id = mp_obj_get_int(args[ARG_id].u_obj); - if (i2c_id < 1 || i2c_id > MP_ARRAY_SIZE(machine_hard_i2c_obj) - || machine_hard_i2c_obj[i2c_id - 1].base.type == NULL) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't exist"), i2c_id); - } - } - // get static peripheral object + int i2c_id = i2c_find_peripheral(args[ARG_id].u_obj); machine_hard_i2c_obj_t *self = (machine_hard_i2c_obj_t *)&machine_hard_i2c_obj[i2c_id - 1]; // here we would check the scl/sda pins and configure them, but it's not implemented diff --git a/ports/stm32/pyb_i2c.c b/ports/stm32/pyb_i2c.c index 4f8f7a0e3741c..ee8b498a1359c 100644 --- a/ports/stm32/pyb_i2c.c +++ b/ports/stm32/pyb_i2c.c @@ -668,39 +668,8 @@ STATIC mp_obj_t pyb_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_ // check arguments mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); - // work out i2c bus - int i2c_id = 0; - if (mp_obj_is_str(args[0])) { - const char *port = mp_obj_str_get_str(args[0]); - if (0) { - #ifdef MICROPY_HW_I2C1_NAME - } else if (strcmp(port, MICROPY_HW_I2C1_NAME) == 0) { - i2c_id = 1; - #endif - #ifdef MICROPY_HW_I2C2_NAME - } else if (strcmp(port, MICROPY_HW_I2C2_NAME) == 0) { - i2c_id = 2; - #endif - #ifdef MICROPY_HW_I2C3_NAME - } else if (strcmp(port, MICROPY_HW_I2C3_NAME) == 0) { - i2c_id = 3; - #endif - #ifdef MICROPY_HW_I2C4_NAME - } else if (strcmp(port, MICROPY_HW_I2C4_NAME) == 0) { - i2c_id = 4; - #endif - } else { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%s) doesn't exist"), port); - } - } else { - i2c_id = mp_obj_get_int(args[0]); - if (i2c_id < 1 || i2c_id > MP_ARRAY_SIZE(pyb_i2c_obj) - || pyb_i2c_obj[i2c_id - 1].i2c == NULL) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't exist"), i2c_id); - } - } - // get I2C object + int i2c_id = i2c_find_peripheral(args[0]); const pyb_i2c_obj_t *i2c_obj = &pyb_i2c_obj[i2c_id - 1]; if (n_args > 1 || n_kw > 0) { From 1e4e2644ecfd2cab5154a2ad95f3eb5aca545ba7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 3 Dec 2020 13:07:37 +1100 Subject: [PATCH 135/179] stm32: Add support for a board to reserve certain peripherals. Allows reserving CAN, I2C, SPI, Timer and UART peripherals. If reserved the peripheral cannot be accessed from Python. Signed-off-by: Damien George --- ports/stm32/i2c.c | 6 ++++++ ports/stm32/machine_uart.c | 5 +++++ ports/stm32/mpconfigboard_common.h | 25 ++++++++++++++++++++++++ ports/stm32/pyb_can.c | 5 +++++ ports/stm32/spi.c | 31 ++++++++++++++++++------------ ports/stm32/timer.c | 5 +++++ 6 files changed, 65 insertions(+), 12 deletions(-) diff --git a/ports/stm32/i2c.c b/ports/stm32/i2c.c index d67201b714418..0b7105344cd28 100644 --- a/ports/stm32/i2c.c +++ b/ports/stm32/i2c.c @@ -534,6 +534,12 @@ int i2c_find_peripheral(mp_obj_t id) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't exist"), i2c_id); } } + + // check if the I2C is reserved for system use or not + if (MICROPY_HW_I2C_IS_RESERVED(i2c_id)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) is reserved"), i2c_id); + } + return i2c_id; } diff --git a/ports/stm32/machine_uart.c b/ports/stm32/machine_uart.c index 2862f4c0e7b15..31e6a1789e4a3 100644 --- a/ports/stm32/machine_uart.c +++ b/ports/stm32/machine_uart.c @@ -345,6 +345,11 @@ STATIC mp_obj_t pyb_uart_make_new(const mp_obj_type_t *type, size_t n_args, size } } + // check if the UART is reserved for system use or not + if (MICROPY_HW_UART_IS_RESERVED(uart_id)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("UART(%d) is reserved"), uart_id); + } + pyb_uart_obj_t *self; if (MP_STATE_PORT(pyb_uart_obj_all)[uart_id - 1] == NULL) { // create new UART object diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index a73a26b1627b4..8890abc59bbe1 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -127,6 +127,31 @@ #define MICROPY_HW_FLASH_FS_LABEL "pybflash" #endif +// Function to determine if the given can_id is reserved for system use or not. +#ifndef MICROPY_HW_CAN_IS_RESERVED +#define MICROPY_HW_CAN_IS_RESERVED(can_id) (false) +#endif + +// Function to determine if the given i2c_id is reserved for system use or not. +#ifndef MICROPY_HW_I2C_IS_RESERVED +#define MICROPY_HW_I2C_IS_RESERVED(i2c_id) (false) +#endif + +// Function to determine if the given spi_id is reserved for system use or not. +#ifndef MICROPY_HW_SPI_IS_RESERVED +#define MICROPY_HW_SPI_IS_RESERVED(spi_id) (false) +#endif + +// Function to determine if the given tim_id is reserved for system use or not. +#ifndef MICROPY_HW_TIM_IS_RESERVED +#define MICROPY_HW_TIM_IS_RESERVED(tim_id) (false) +#endif + +// Function to determine if the given uart_id is reserved for system use or not. +#ifndef MICROPY_HW_UART_IS_RESERVED +#define MICROPY_HW_UART_IS_RESERVED(uart_id) (false) +#endif + /*****************************************************************************/ // General configuration diff --git a/ports/stm32/pyb_can.c b/ports/stm32/pyb_can.c index 224b8f28b0eab..def66481cbdc8 100644 --- a/ports/stm32/pyb_can.c +++ b/ports/stm32/pyb_can.c @@ -203,6 +203,11 @@ STATIC mp_obj_t pyb_can_make_new(const mp_obj_type_t *type, size_t n_args, size_ mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("CAN(%d) doesn't exist"), can_idx); } + // check if the CAN is reserved for system use or not + if (MICROPY_HW_CAN_IS_RESERVED(can_idx)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("CAN(%d) is reserved"), can_idx); + } + pyb_can_obj_t *self; if (MP_STATE_PORT(pyb_can_obj_all)[can_idx - 1] == NULL) { self = m_new_obj(pyb_can_obj_t); diff --git a/ports/stm32/spi.c b/ports/stm32/spi.c index 5f9b1c1a28d7f..07374d7a38eef 100644 --- a/ports/stm32/spi.c +++ b/ports/stm32/spi.c @@ -167,45 +167,52 @@ void spi_init0(void) { } int spi_find_index(mp_obj_t id) { + int spi_id; if (mp_obj_is_str(id)) { // given a string id const char *port = mp_obj_str_get_str(id); if (0) { #ifdef MICROPY_HW_SPI1_NAME } else if (strcmp(port, MICROPY_HW_SPI1_NAME) == 0) { - return 1; + spi_id = 1; #endif #ifdef MICROPY_HW_SPI2_NAME } else if (strcmp(port, MICROPY_HW_SPI2_NAME) == 0) { - return 2; + spi_id = 2; #endif #ifdef MICROPY_HW_SPI3_NAME } else if (strcmp(port, MICROPY_HW_SPI3_NAME) == 0) { - return 3; + spi_id = 3; #endif #ifdef MICROPY_HW_SPI4_NAME } else if (strcmp(port, MICROPY_HW_SPI4_NAME) == 0) { - return 4; + spi_id = 4; #endif #ifdef MICROPY_HW_SPI5_NAME } else if (strcmp(port, MICROPY_HW_SPI5_NAME) == 0) { - return 5; + spi_id = 5; #endif #ifdef MICROPY_HW_SPI6_NAME } else if (strcmp(port, MICROPY_HW_SPI6_NAME) == 0) { - return 6; + spi_id = 6; #endif + } else { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("SPI(%s) doesn't exist"), port); } - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("SPI(%s) doesn't exist"), port); } else { // given an integer id - int spi_id = mp_obj_get_int(id); - if (spi_id >= 1 && spi_id <= MP_ARRAY_SIZE(spi_obj) - && spi_obj[spi_id - 1].spi != NULL) { - return spi_id; + spi_id = mp_obj_get_int(id); + if (spi_id < 1 || spi_id > MP_ARRAY_SIZE(spi_obj) || spi_obj[spi_id - 1].spi == NULL) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("SPI(%d) doesn't exist"), spi_id); } - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("SPI(%d) doesn't exist"), spi_id); } + + // check if the SPI is reserved for system use or not + if (MICROPY_HW_SPI_IS_RESERVED(spi_id)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("SPI(%d) is reserved"), spi_id); + } + + return spi_id; } STATIC uint32_t spi_get_source_freq(SPI_HandleTypeDef *spi) { diff --git a/ports/stm32/timer.c b/ports/stm32/timer.c index 9b8c14c0da552..a34d2984da979 100644 --- a/ports/stm32/timer.c +++ b/ports/stm32/timer.c @@ -896,6 +896,11 @@ STATIC mp_obj_t pyb_timer_make_new(const mp_obj_type_t *type, size_t n_args, siz mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("Timer(%d) doesn't exist"), tim_id); } + // check if the timer is reserved for system use or not + if (MICROPY_HW_TIM_IS_RESERVED(tim_id)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("Timer(%d) is reserved"), tim_id); + } + pyb_timer_obj_t *tim; if (MP_STATE_PORT(pyb_timer_obj_all)[tim_id - 1] == NULL) { // create new Timer object From cb1bb7592e18a50f33d22f84614c5ee9f45ce21e Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 7 Dec 2020 17:11:49 +1100 Subject: [PATCH 136/179] stm32/Makefile: Change -O0 to -Og for DEBUG=1 builds. The -Og optimisation level produces a more realistic build, gives a better debugging experience, and generates smaller code than -O0, allowing debug builds to fit in flash. This commit also assigns variables in can.c to prevent warnings when -Og is used, and builds a board in CI with DEBUG=1 enabled. Signed-off-by: Damien George --- ports/stm32/Makefile | 2 +- ports/stm32/can.c | 4 ++-- tools/ci.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index c3b7bbba98c61..a9dac03d2c021 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -133,7 +133,7 @@ LDFLAGS += --gc-sections # Debugging/Optimization ifeq ($(DEBUG), 1) CFLAGS += -g -DPENDSV_DEBUG -COPT = -O0 +COPT = -Og # Disable text compression in debug builds MICROPY_ROM_TEXT_COMPRESSION = 0 else diff --git a/ports/stm32/can.c b/ports/stm32/can.c index e5fc55e409bbc..bcc6983594967 100644 --- a/ports/stm32/can.c +++ b/ports/stm32/can.c @@ -226,8 +226,8 @@ int can_receive(CAN_HandleTypeDef *can, int fifo, CanRxMsgTypeDef *msg, uint8_t HAL_StatusTypeDef CAN_Transmit(CAN_HandleTypeDef *hcan, uint32_t Timeout) { uint32_t transmitmailbox; uint32_t tickstart; - uint32_t rqcpflag; - uint32_t txokflag; + uint32_t rqcpflag = 0; + uint32_t txokflag = 0; // Check the parameters assert_param(IS_CAN_IDTYPE(hcan->pTxMsg->IDE)); diff --git a/tools/ci.sh b/tools/ci.sh index 3b906b449c112..ded9fab922d50 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -200,7 +200,7 @@ function ci_stm32_nucleo_build { make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_F091RC make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_H743ZI CFLAGS_EXTRA='-DMICROPY_PY_THREAD=1' make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_L073RZ - make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_L476RG + make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_L476RG DEBUG=1 make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_WB55 make ${MAKEOPTS} -C ports/stm32/mboot BOARD=NUCLEO_WB55 } From 92a5ee6ac1c50084f8c29d4accda67cca91b6846 Mon Sep 17 00:00:00 2001 From: Maureen Helm Date: Mon, 30 Nov 2020 10:45:05 -0600 Subject: [PATCH 137/179] zephyr: Replace broken shell_net_iface() with more general shell_exec(). The zephyr function net_shell_cmd_iface() was removed in zephyr v1.14.0, therefore the MicroPython zephyr port did not build with newer zephyr versions when CONFIG_NET_SHELL=y. Replace with a more general shell_exec() function that can execute any zephyr shell command. For example: >>> zephyr.shell_exec("net") Subcommands: allocs :Print network memory allocations. arp :Print information about IPv4 ARP cache. conn :Print information about network connections. dns :Show how DNS is configured. events :Monitor network management events. gptp :Print information about gPTP support. iface :Print information about network interfaces. ipv6 :Print information about IPv6 specific information and configuration. mem :Print information about network memory usage. nbr :Print neighbor information. ping :Ping a network host. pkt :net_pkt information. ppp :PPP information. resume :Resume a network interface route :Show network route. stacks :Show network stacks information. stats :Show network statistics. suspend :Suspend a network interface tcp :Connect/send/close TCP connection. vlan :Show VLAN information. websocket :Print information about WebSocket connections. >>> zephyr.shell_exec("kernel") kernel - Kernel commands Subcommands: cycles :Kernel cycles. reboot :Reboot. stacks :List threads stack usage. threads :List kernel threads. uptime :Kernel uptime. version :Kernel version. Signed-off-by: Maureen Helm --- ports/zephyr/modzephyr.c | 22 ++++++++++------------ ports/zephyr/prj.conf | 6 +++++- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/ports/zephyr/modzephyr.c b/ports/zephyr/modzephyr.c index 71b44d7df1611..52449ea381c42 100644 --- a/ports/zephyr/modzephyr.c +++ b/ports/zephyr/modzephyr.c @@ -31,6 +31,8 @@ #include #include #include +#include +#include #include "modzephyr.h" #include "py/runtime.h" @@ -53,17 +55,14 @@ STATIC mp_obj_t mod_thread_analyze(void) { STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_thread_analyze_obj, mod_thread_analyze); #endif -#ifdef CONFIG_NET_SHELL - -// int net_shell_cmd_iface(int argc, char *argv[]); - -STATIC mp_obj_t mod_shell_net_iface(void) { - net_shell_cmd_iface(0, NULL); +#ifdef CONFIG_SHELL_BACKEND_SERIAL +STATIC mp_obj_t mod_shell_exec(mp_obj_t cmd_in) { + const char *cmd = mp_obj_str_get_str(cmd_in); + shell_execute_cmd(shell_backend_uart_get_ptr(), cmd); return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_shell_net_iface_obj, mod_shell_net_iface); - -#endif // CONFIG_NET_SHELL +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_shell_exec_obj, mod_shell_exec); +#endif // CONFIG_SHELL_BACKEND_SERIAL STATIC const mp_rom_map_elem_t mp_module_time_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_zephyr) }, @@ -72,9 +71,8 @@ STATIC const mp_rom_map_elem_t mp_module_time_globals_table[] = { #ifdef CONFIG_THREAD_ANALYZER { MP_ROM_QSTR(MP_QSTR_thread_analyze), MP_ROM_PTR(&mod_thread_analyze_obj) }, #endif - - #ifdef CONFIG_NET_SHELL - { MP_ROM_QSTR(MP_QSTR_shell_net_iface), MP_ROM_PTR(&mod_shell_net_iface_obj) }, + #ifdef CONFIG_SHELL_BACKEND_SERIAL + { MP_ROM_QSTR(MP_QSTR_shell_exec), MP_ROM_PTR(&mod_shell_exec_obj) }, #endif #ifdef CONFIG_DISK_ACCESS { MP_ROM_QSTR(MP_QSTR_DiskAccess), MP_ROM_PTR(&zephyr_disk_access_type) }, diff --git a/ports/zephyr/prj.conf b/ports/zephyr/prj.conf index 50cfa005025dc..9824f48e3362f 100644 --- a/ports/zephyr/prj.conf +++ b/ports/zephyr/prj.conf @@ -57,7 +57,11 @@ CONFIG_THREAD_NAME=y # Required for usocket.pkt_get_info() CONFIG_NET_BUF_POOL_USAGE=y -# Required for usocket.shell_*() +# Required for zephyr.shell_exec() +#CONFIG_SHELL=y +#CONFIG_SHELL_BACKEND_SERIAL_INTERRUPT_DRIVEN=n + +# Required for zephyr.shell_exec("net iface") #CONFIG_NET_SHELL=y # Uncomment to enable "INFO" level net_buf logging From dde0735ac1629aca4f7d41334f25b75dd8d35010 Mon Sep 17 00:00:00 2001 From: Jonathan Bruchim Date: Sun, 8 Nov 2020 20:31:35 +0200 Subject: [PATCH 138/179] zephyr: Guard I2C code with appropriate ifdef config. To reduce binary code size when not using I2C. Signed-off-by: Jonathan Bruchim --- ports/zephyr/machine_i2c.c | 4 ++++ ports/zephyr/modmachine.c | 2 ++ 2 files changed, 6 insertions(+) diff --git a/ports/zephyr/machine_i2c.c b/ports/zephyr/machine_i2c.c index efd4bdcb21b58..aa8823392dff0 100644 --- a/ports/zephyr/machine_i2c.c +++ b/ports/zephyr/machine_i2c.c @@ -39,6 +39,8 @@ #include "extmod/machine_i2c.h" #include "modmachine.h" +#if MICROPY_PY_MACHINE_I2C + typedef struct _machine_hard_i2c_obj_t { mp_obj_base_t base; const struct device *dev; @@ -136,3 +138,5 @@ const mp_obj_type_t machine_hard_i2c_type = { .protocol = &machine_hard_i2c_p, .locals_dict = (mp_obj_dict_t *)&mp_machine_i2c_locals_dict, }; + +#endif // MICROPY_PY_MACHINE_I2C diff --git a/ports/zephyr/modmachine.c b/ports/zephyr/modmachine.c index 29e6c889c4cef..968f758b93ed3 100644 --- a/ports/zephyr/modmachine.c +++ b/ports/zephyr/modmachine.c @@ -60,7 +60,9 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { #endif { MP_ROM_QSTR(MP_QSTR_reset_cause), MP_ROM_PTR(&machine_reset_cause_obj) }, + #if MICROPY_PY_MACHINE_I2C { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_hard_i2c_type) }, + #endif { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&machine_pin_type) }, { MP_ROM_QSTR(MP_QSTR_Signal), MP_ROM_PTR(&machine_signal_type) }, From 5020b14d5419065f1a5ef5aed1be7badee28c9bf Mon Sep 17 00:00:00 2001 From: Joris Peeraer Date: Thu, 22 Oct 2020 10:38:03 +0200 Subject: [PATCH 139/179] py/mpprint: Fix length calculation for strings with precision-modifier. Two issues are tackled: 1. The calculation of the correct length to print is fixed to treat the precision as a maximum length instead as the exact length. This is done for both qstr (%q) and for regular str (%s). 2. Fix the incorrect use of mp_printf("%.*s") to mp_print_strn(). Because of the fix of above issue, some testcases that would print an embedded null-byte (^@ in test-output) would now fail. The bug here is that "%s" was used to print null-bytes. Instead, mp_print_strn is used to make sure all bytes are outputted and the exact length is respected. Test-cases are added for both %s and %q with a combination of precision and padding specifiers. --- ports/unix/coverage.c | 2 +- py/mpprint.c | 13 +++++++------ py/objstr.c | 4 ++-- py/objstrunicode.c | 2 +- tests/unix/extra_coverage.py.exp | 2 +- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/ports/unix/coverage.c b/ports/unix/coverage.c index 7169c7793bd93..ef66c4fb59440 100644 --- a/ports/unix/coverage.c +++ b/ports/unix/coverage.c @@ -186,7 +186,7 @@ STATIC mp_obj_t extra_coverage(void) { mp_printf(&mp_plat_print, "%ld\n", 123); // long mp_printf(&mp_plat_print, "%lx\n", 0x123); // long hex mp_printf(&mp_plat_print, "%X\n", 0x1abcdef); // capital hex - mp_printf(&mp_plat_print, "%.2s %.3s\n", "abc", "abc"); // fixed string precision + mp_printf(&mp_plat_print, "%.2s %.3s '%4.4s' '%5.5q' '%.3q'\n", "abc", "abc", "abc", MP_QSTR_True, MP_QSTR_True); // fixed string precision mp_printf(&mp_plat_print, "%.*s\n", -1, "abc"); // negative string precision mp_printf(&mp_plat_print, "%b %b\n", 0, 1); // bools #ifndef NDEBUG diff --git a/py/mpprint.c b/py/mpprint.c index 31cf73bb58af9..3218bd2f4dcf8 100644 --- a/py/mpprint.c +++ b/py/mpprint.c @@ -484,10 +484,10 @@ int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args) { qstr qst = va_arg(args, qstr); size_t len; const char *str = (const char *)qstr_data(qst, &len); - if (prec < 0) { - prec = len; + if (prec >= 0 && (size_t)prec < len) { + len = prec; } - chrs += mp_print_strn(print, str, prec, flags, fill, width); + chrs += mp_print_strn(print, str, len, flags, fill, width); break; } case 's': { @@ -499,10 +499,11 @@ int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args) { break; } #endif - if (prec < 0) { - prec = strlen(str); + size_t len = strlen(str); + if (prec >= 0 && (size_t)prec < len) { + len = prec; } - chrs += mp_print_strn(print, str, prec, flags, fill, width); + chrs += mp_print_strn(print, str, len, flags, fill, width); break; } case 'd': { diff --git a/py/objstr.c b/py/objstr.c index 84728e6f2d919..9f8da873d66c0 100644 --- a/py/objstr.c +++ b/py/objstr.c @@ -123,10 +123,10 @@ STATIC void str_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t bool is_bytes = true; #endif if (kind == PRINT_RAW || (!MICROPY_PY_BUILTINS_STR_UNICODE && kind == PRINT_STR && !is_bytes)) { - mp_printf(print, "%.*s", str_len, str_data); + print->print_strn(print->data, (const char *)str_data, str_len); } else { if (is_bytes) { - mp_print_str(print, "b"); + print->print_strn(print->data, "b", 1); } mp_str_print_quoted(print, str_data, str_len, is_bytes); } diff --git a/py/objstrunicode.c b/py/objstrunicode.c index b21df22a3bc0a..ed79ad68a93c6 100644 --- a/py/objstrunicode.c +++ b/py/objstrunicode.c @@ -92,7 +92,7 @@ STATIC void uni_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t } #endif if (kind == PRINT_STR) { - mp_printf(print, "%.*s", str_len, str_data); + print->print_strn(print->data, (const char *)str_data, str_len); } else { uni_print_quoted(print, str_data, str_len); } diff --git a/tests/unix/extra_coverage.py.exp b/tests/unix/extra_coverage.py.exp index 257224108a4c5..d97de2c0ef8b3 100644 --- a/tests/unix/extra_coverage.py.exp +++ b/tests/unix/extra_coverage.py.exp @@ -4,7 +4,7 @@ 123 123 1ABCDEF -ab abc +ab abc ' abc' ' True' 'Tru' false true (null) From 248968863595fb285b1209927e27c37b06faf857 Mon Sep 17 00:00:00 2001 From: Glenn Ruben Bakke Date: Thu, 16 Jul 2020 22:19:19 +0200 Subject: [PATCH 140/179] nrf/boards: Update memory.ld to include bootloader offsets. Adding variables that can be set from other linker scripts: - _bootloader_head_size: Bootloader flash offset in front of the application. - _bootloader_tail_size: Bootloader offset from the tail of the flash. In case the bootloader is located at the end. - _bootloader_head_ram_size: Bootloader RAM usage in front of the application. Updated calculations of application flash and RAM. --- ports/nrf/boards/memory.ld | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/ports/nrf/boards/memory.ld b/ports/nrf/boards/memory.ld index f1f9a2a4c48b8..9c4c9c8bd1267 100644 --- a/ports/nrf/boards/memory.ld +++ b/ports/nrf/boards/memory.ld @@ -1,18 +1,20 @@ -/* Flash layout: softdevice | application | filesystem */ -/* RAM layout: softdevice RAM | application RAM */ - -_ram_start = DEFINED(_ram_start) ? _ram_start : 0x20000000; -_flash_start = DEFINED(_flash_start) ? _flash_start : 0; -_sd_size = DEFINED(_sd_size) ? _sd_size : _flash_start; +/* Flash layout: bootloader_head | softdevice | application | filesystem | bootloader_tail */ +/* RAM layout: bootloader RAM | softdevice RAM | application RAM */ +_bootloader_head_size = DEFINED(_bootloader_head_size) ? _bootloader_head_size : 0; +_bootloader_tail_size = DEFINED(_bootloader_tail_size) ? _bootloader_tail_size : 0; +_bootloader_head_ram_size = DEFINED(_bootloader_head_ram_size) ? _bootloader_head_ram_size : 0; +_head_size = DEFINED(_sd_size) ? _sd_size : _bootloader_head_size; +_head_ram = DEFINED(_sd_ram) ? _sd_ram : _bootloader_head_ram_size; +_sd_size = DEFINED(_sd_size) ? _sd_size : 0; _sd_ram = DEFINED(_sd_ram) ? _sd_ram : 0; _fs_size = DEFINED(_fs_size) ? _fs_size : 64K; /* TODO: set to 0 if not using the filesystem */ -_app_size = _flash_size - _sd_size - _fs_size; -_app_start = _sd_size; -_fs_start = _sd_size + _app_size; +_app_size = _flash_size - _head_size - _fs_size - _bootloader_tail_size; +_app_start = _head_size; +_fs_start = _head_size + _app_size; _fs_end = _fs_start + _fs_size; -_app_ram_start = _ram_start + _sd_ram; -_app_ram_size = _ram_size - _sd_ram; +_app_ram_start = 0x20000000 + _head_ram; +_app_ram_size = _ram_size - _head_ram; _heap_start = _ebss; _heap_end = _ram_end - _stack_size; _heap_size = _heap_end - _heap_start; From 718397a37d7ec2071b5f5d3668c31e6b8f69f3b9 Mon Sep 17 00:00:00 2001 From: Glenn Ruben Bakke Date: Thu, 16 Jul 2020 23:04:02 +0200 Subject: [PATCH 141/179] nrf/Makefile: Add bootloader specific section. Add the option for "mpconfigboard.mk" to define whether the board hosts a bootloader or not. The BOOTLOADER make variable must be set to the name of the bootloader. When the BOOTLOADER name is set it is also required to supply the BOOTLOADER_VERSION_MAJOR and the BOOTLOADER_VERSION_MINOR from the "mpconfigboards.mk". These will be used to resolve which bootloader linker script that should be passed to the linker. The BOOTLOADER section also supplies the C-compiler with BOOTLOADER_= as a compiler define. This is for future use in case a bootloader needs to do modification to the startup files or similar (like setting the VTOR specific to a version of a bootloader). --- ports/nrf/Makefile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ports/nrf/Makefile b/ports/nrf/Makefile index e5a1b471a35d7..4ee9384ec28ff 100644 --- a/ports/nrf/Makefile +++ b/ports/nrf/Makefile @@ -26,6 +26,13 @@ else include drivers/bluetooth/bluetooth_common.mk endif +ifneq ($(BOOTLOADER),) + BOOTLOADER_UPPER = $(shell echo $(BOOTLOADER) | tr '[:lower:]' '[:upper:]') + # Use additional bootloader linker script + LD_FILES += boards/$(MCU_SUB_VARIANT)_$(BOOTLOADER)_$(BOOTLOADER_VERSION_MAJOR).$(BOOTLOADER_VERSION_MINOR).x.ld + CFLAGS += -DBOOTLOADER_$(BOOTLOADER_UPPER)_VERSION=$(BOOTLOADER_MAJOR)$(BOOTLOADER_MINOR) +endif + LD_FILES += boards/memory.ld boards/common.ld ifneq ($(LD_FILE),) From 634f6df32485457dd1c236633cffd49177aaf826 Mon Sep 17 00:00:00 2001 From: Glenn Ruben Bakke Date: Thu, 16 Jul 2020 23:17:28 +0200 Subject: [PATCH 142/179] nrf/Makefile: Add support for flashing with nrfutil. An additional Makefile parameter NRFUTIL_PORT can be set in order to define the serial port to used for the DFU (Default: /dev/ttyACM0). The "nrfutil" that is used as flasher towards OpenBootloader is available for installation through Python "pip". In case of SD=s140, SoftDevice ID 0xB6 is passed to nrfutil's package generation which corresponds to SoftDevice s140 v6.1.1. --- ports/nrf/Makefile | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/ports/nrf/Makefile b/ports/nrf/Makefile index 4ee9384ec28ff..86809508042c1 100644 --- a/ports/nrf/Makefile +++ b/ports/nrf/Makefile @@ -446,6 +446,31 @@ deploy: $(BUILD)/$(OUTPUT_FILENAME).hex sd: $(BUILD)/$(OUTPUT_FILENAME).hex $(Q)$(OPENOCD) -f interface/cmsis-dap.cfg -f $(OPENOCD_TARGET) -c "init" -c "program $(SOFTDEV_HEX) verify reset" -c "exit" +else ifeq ($(FLASHER), nrfutil) + +NRFUTIL_PORT ?= /dev/ttyACM0 +NRFUTIL_SD_REQ ?= 0x00 + +ifeq ($(SD), s140) +NRFUTIL_SD_REQ = 0xB6 +endif + +.PHONY: nrfutil_dfu_sd nrfutil_dfu_deploy +.NOTPARALLEL: nrfutil_dfu_sd nrfutil_dfu_deploy + +# DFU both SD and app in case of target "sd" +sd: nrfutil_dfu_sd nrfutil_dfu_deploy + +nrfutil_dfu_sd: $(BUILD)/$(OUTPUT_FILENAME).hex + $(Q)hexmerge.py -o $(BUILD)/stripped_sd.hex --range=0x1000: $(SOFTDEV_HEX) + $(Q)nrfutil pkg generate --hw-version 52 --sd-req 0x00 --sd-id 0x00 --softdevice $(BUILD)/stripped_sd.hex $(BUILD)/stripped_sd.zip + $(Q)nrfutil dfu usb-serial -pkg $(BUILD)/stripped_sd.zip -p $(NRFUTIL_PORT) -t 0 + +nrfutil_dfu_deploy: $(BUILD)/$(OUTPUT_FILENAME).hex + $(Q)nrfutil pkg generate --hw-version 52 --sd-req $(NRFUTIL_SD_REQ) --application-version 1 --application $(BUILD)/$(OUTPUT_FILENAME).hex $(BUILD)/$(OUTPUT_FILENAME)_dfu.zip + $(Q)nrfutil dfu usb-serial -pkg $(BUILD)/$(OUTPUT_FILENAME)_dfu.zip -p $(NRFUTIL_PORT) -t 0 + +deploy: nrfutil_dfu_deploy endif flash: deploy From 7f405236a3355467a37cb16762939d6362352089 Mon Sep 17 00:00:00 2001 From: Glenn Ruben Bakke Date: Thu, 16 Jul 2020 23:23:41 +0200 Subject: [PATCH 143/179] nrf/boards: Add linker script for nrf52840 Open Bootloader 1.2.0. --- ports/nrf/boards/nrf52840_open_bootloader_1.2.x.ld | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 ports/nrf/boards/nrf52840_open_bootloader_1.2.x.ld diff --git a/ports/nrf/boards/nrf52840_open_bootloader_1.2.x.ld b/ports/nrf/boards/nrf52840_open_bootloader_1.2.x.ld new file mode 100644 index 0000000000000..c23a2dbda411f --- /dev/null +++ b/ports/nrf/boards/nrf52840_open_bootloader_1.2.x.ld @@ -0,0 +1,5 @@ +/* GNU linker script for nrf52840 Open Bootloader version 1.2.x */ + +_bootloader_head_size = 0x1000; /* MBR */ +_bootloader_tail_size = 0x20000; /* Bootloader start address 0x000E0000 */ +_bootloader_head_ram_size = 0x8; From d0b8554df477ca1b9daf634d01604e3f760909ca Mon Sep 17 00:00:00 2001 From: Glenn Ruben Bakke Date: Thu, 3 Dec 2020 21:22:27 +0100 Subject: [PATCH 144/179] nrf: Change selected boards to utilize pre-flashed bootloader. The nrf52840-mdk-usb-dongle and pca10050 comes with a pre-flashed bootloader (OpenBootloader). This commit updates the boards "mpconfigboard.mk" to use DFU as default flashing method and set the corresponding BOOTLOADER settings such that nrf52840_open_bootloader_1.2.x.ld linker script is used. The default DFU flashing method can be disabled by issuing "DFU=0" when invoking make. This will lead to "segger" being used as default flashing tool. When using "DFU=0", the linker scripts will not compensate for any MBR and Bootloader region being present, and might overwrite them if they were present. The commit also removes the custom linker script specific to nrf52840-mdk-usb-dongle as it now points to a generic. Updated nrf52840-mdk-usb-dongle's README.md to be more clear on how to deploy the built firmware. The port README.md has also been updated. In the list of target boards a new column has been added to indicate which bootloader is present on the target board. And for consistency, changed all examples in the README.md to use "deploy" instead of "flash". --- ports/nrf/README.md | 70 +++++++++++++------ .../boards/nrf52840-mdk-usb-dongle/README.md | 9 ++- .../nrf52840-mdk-usb-dongle/mpconfigboard.mk | 14 +++- .../nrf52840_open_bootloader.ld | 2 - ports/nrf/boards/pca10059/mpconfigboard.mk | 12 ++++ 5 files changed, 77 insertions(+), 30 deletions(-) delete mode 100644 ports/nrf/boards/nrf52840-mdk-usb-dongle/nrf52840_open_bootloader.ld diff --git a/ports/nrf/README.md b/ports/nrf/README.md index 5fceb6705f6d2..22bb4af514a39 100644 --- a/ports/nrf/README.md +++ b/ports/nrf/README.md @@ -58,12 +58,12 @@ By default, the PCA10040 (nrf52832) is used as compile target. To build and flas make submodules make - make flash + make deploy Alternatively the target board could be defined: make BOARD=pca10040 - make BOARD=pca10040 flash + make BOARD=pca10040 deploy ## Compile without LTO enabled @@ -118,26 +118,27 @@ For example: ## Target Boards and Make Flags -Target Board (BOARD) | Bluetooth Stack (SD) | Bluetooth Support | Flash Util ----------------------|-------------------------|------------------------|------------------------------- -microbit | s110 | Peripheral | [PyOCD](#pyocdopenocd-targets) -pca10000 | s110 | Peripheral | [Segger](#segger-targets) -pca10001 | s110 | Peripheral | [Segger](#segger-targets) -pca10028 | s110 | Peripheral | [Segger](#segger-targets) -pca10031 | s110 | Peripheral | [Segger](#segger-targets) -wt51822_s4at | s110 | Peripheral | Manual, see [datasheet](https://4tronix.co.uk/picobot2/WT51822-S4AT.pdf) for pinout -pca10040 | s132 | Peripheral and Central | [Segger](#segger-targets) -feather52 | s132 | Peripheral and Central | Manual, SWDIO and SWCLK solder points on the bottom side of the board -arduino_primo | s132 | Peripheral and Central | [PyOCD](#pyocdopenocd-targets) -ibk_blyst_nano | s132 | Peripheral and Central | [IDAP](#idap-midap-link-targets) -idk_blyst_nano | s132 | Peripheral and Central | [IDAP](#idap-midap-link-targets) -blueio_tag_evim | s132 | Peripheral and Central | [IDAP](#idap-midap-link-targets) -evk_nina_b1 | s132 | Peripheral and Central | [Segger](#segger-targets) -pca10056 | s140 | Peripheral and Central | [Segger](#segger-targets) -pca10059 | s140 | Peripheral and Central | Manual, SWDIO and SWCLK solder points on the sides. -particle_xenon | s140 | Peripheral and Central | [Black Magic Probe](#black-magic-probe-targets) -pca10090 | None (bsdlib.a) | None (LTE/GNSS) | [Segger](#segger-targets) -actinius_icarus | None (bsdlib.a) | None (LTE/GNSS) | [Segger](#segger-targets) +Target Board (BOARD) | Bluetooth Stack (SD) | Bluetooth Support | Bootloader | Default Flash Util +---------------------|-------------------------|------------------------|----------------|------------------- +microbit | s110 | Peripheral | | [PyOCD](#pyocdopenocd-targets) +pca10000 | s110 | Peripheral | | [Segger](#segger-targets) +pca10001 | s110 | Peripheral | | [Segger](#segger-targets) +pca10028 | s110 | Peripheral | | [Segger](#segger-targets) +pca10031 | s110 | Peripheral | | [Segger](#segger-targets) +wt51822_s4at | s110 | Peripheral | | Manual, see [datasheet](https://4tronix.co.uk/picobot2/WT51822-S4AT.pdf) for pinout +pca10040 | s132 | Peripheral and Central | | [Segger](#segger-targets) +feather52 | s132 | Peripheral and Central | | Manual, SWDIO and SWCLK solder points on the bottom side of the board +arduino_primo | s132 | Peripheral and Central | | [PyOCD](#pyocdopenocd-targets) +ibk_blyst_nano | s132 | Peripheral and Central | | [IDAP](#idap-midap-link-targets) +idk_blyst_nano | s132 | Peripheral and Central | | [IDAP](#idap-midap-link-targets) +blueio_tag_evim | s132 | Peripheral and Central | | [IDAP](#idap-midap-link-targets) +evk_nina_b1 | s132 | Peripheral and Central | | [Segger](#segger-targets) +pca10056 | s140 | Peripheral and Central | | [Segger](#segger-targets) +pca10059 | s140 | Peripheral and Central | OpenBootloader | [nrfutil](#nrfutil-targets) +particle_xenon | s140 | Peripheral and Central | | [Black Magic Probe](#black-magic-probe-targets) +nrf52840-mdk-usb-dongle | s140 | Peripheral and Central | OpenBootloader | [nrfutil](#nrfutil-targets) +pca10090 | None (bsdlib.a) | None (LTE/GNSS) | | [Segger](#segger-targets) +actinius_icarus | None (bsdlib.a) | None (LTE/GNSS) | | [Segger](#segger-targets) ## IDAP-M/IDAP-Link Targets @@ -173,6 +174,31 @@ This requires no further dependencies other than `arm-none-eabi-gdb`. [this guide](https://github.com/blacksphere/blackmagic/wiki/Useful-GDB-commands) for more tips about using the BMP with GDB. +## nRFUtil Targets + +Install the necessary Python packages that will be used for flashing using the bootloader: + + sudo pip install nrfutil + sudo pip install intelhex + +The `intelhex` provides the `hexmerge.py` utility which is used by the Makefile +to trim of the MBR in case SoftDevice flashing is requested. + +`nrfutil` as flashing backend also requires a serial port paramter to be defined +in addition to the `deploy` target of make. For example: + + make BOARD=nrf52840-mdk-usb-dongle NRFUTIL_PORT=/dev/ttyACM0 deploy + +If the target device is connected to `/dev/ttyACM0` serial port, the +`NRFUTIL_PORT` parameter to make can be elided as it is the default serial +port set by the Makefile. + +When enabling Bluetooth LE, as with the other flash utils, the SoftDevice +needs to be flashed in the first firmware update. This can be done by issuing +the `sd` target instead of `deploy`. For example: + + make BOARD=nrf52840-mdk-usb-dongle SD=s140 NRFUTIL_PORT=/dev/ttyACM0 sd + ## Bluetooth LE REPL The port also implements a BLE REPL driver. This feature is disabled by default, as it will deactivate the UART REPL when activated. As some of the nRF devices only have one UART, using the BLE REPL free's the UART instance such that it can be used as a general UART peripheral not bound to REPL. diff --git a/ports/nrf/boards/nrf52840-mdk-usb-dongle/README.md b/ports/nrf/boards/nrf52840-mdk-usb-dongle/README.md index c39a800d776cf..f993c7dc53623 100644 --- a/ports/nrf/boards/nrf52840-mdk-usb-dongle/README.md +++ b/ports/nrf/boards/nrf52840-mdk-usb-dongle/README.md @@ -39,11 +39,10 @@ Follow the standard [nRF Port build instructions](../../README.md); but use make BOARD=nrf52840-mdk-usb-dongle The build artifacts will be created in `build-nrf52840-mdk-usb-dongle`. Once -built, the easiest way to deploy to the device is to open `firmware.hex` using +built, the target can be deployed to the device as described in +[nRFUtil targets](../../README.md#nrfutil-targets). + +An alternative way to deploy to the device, is to open `firmware.hex` using *nRF Connect* and select *Write*. Detailed instructions can be found on the [developer wiki](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/programming/). - -**Note** that the regular method of deployment for the MicroPython nRF port -(using `make deploy`) will *not* operate correctly and will overwrite the -bootloader. diff --git a/ports/nrf/boards/nrf52840-mdk-usb-dongle/mpconfigboard.mk b/ports/nrf/boards/nrf52840-mdk-usb-dongle/mpconfigboard.mk index f98d5c88a9076..ca437418d5254 100644 --- a/ports/nrf/boards/nrf52840-mdk-usb-dongle/mpconfigboard.mk +++ b/ports/nrf/boards/nrf52840-mdk-usb-dongle/mpconfigboard.mk @@ -2,6 +2,18 @@ MCU_SERIES = m4 MCU_VARIANT = nrf52 MCU_SUB_VARIANT = nrf52840 SOFTDEV_VERSION = 6.1.1 -LD_FILES += boards/nrf52840-mdk-usb-dongle/nrf52840_open_bootloader.ld boards/nrf52840_1M_256k.ld + +DFU ?= 1 + +ifeq ($(DFU),1) +BOOTLOADER=open_bootloader +BOOTLOADER_VERSION_MAJOR=1 +BOOTLOADER_VERSION_MINOR=2 +FLASHER=nrfutil +else +FLASHER=segger +endif + +LD_FILES += boards/nrf52840_1M_256k.ld NRF_DEFINES += -DNRF52840_XXAA diff --git a/ports/nrf/boards/nrf52840-mdk-usb-dongle/nrf52840_open_bootloader.ld b/ports/nrf/boards/nrf52840-mdk-usb-dongle/nrf52840_open_bootloader.ld deleted file mode 100644 index d6c6e743a4daf..0000000000000 --- a/ports/nrf/boards/nrf52840-mdk-usb-dongle/nrf52840_open_bootloader.ld +++ /dev/null @@ -1,2 +0,0 @@ -_ram_start = 0x20000008; -_flash_start = 0x1000; diff --git a/ports/nrf/boards/pca10059/mpconfigboard.mk b/ports/nrf/boards/pca10059/mpconfigboard.mk index ca555d3932aa4..ca437418d5254 100644 --- a/ports/nrf/boards/pca10059/mpconfigboard.mk +++ b/ports/nrf/boards/pca10059/mpconfigboard.mk @@ -2,6 +2,18 @@ MCU_SERIES = m4 MCU_VARIANT = nrf52 MCU_SUB_VARIANT = nrf52840 SOFTDEV_VERSION = 6.1.1 + +DFU ?= 1 + +ifeq ($(DFU),1) +BOOTLOADER=open_bootloader +BOOTLOADER_VERSION_MAJOR=1 +BOOTLOADER_VERSION_MINOR=2 +FLASHER=nrfutil +else +FLASHER=segger +endif + LD_FILES += boards/nrf52840_1M_256k.ld NRF_DEFINES += -DNRF52840_XXAA From cd61fc8e44202feb20a4c52b56f75740aa72d075 Mon Sep 17 00:00:00 2001 From: Reinhard Feger Date: Fri, 24 Jul 2020 22:53:43 +0200 Subject: [PATCH 145/179] stm32/boards/stm32h743.ld: Enable D2 RAM and add eth-buffer section. --- ports/stm32/boards/stm32h743.ld | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/stm32h743.ld b/ports/stm32/boards/stm32h743.ld index 69738ab8b913a..8cf8a4e591a5f 100644 --- a/ports/stm32/boards/stm32h743.ld +++ b/ports/stm32/boards/stm32h743.ld @@ -10,7 +10,8 @@ MEMORY FLASH_FS (r) : ORIGIN = 0x08020000, LENGTH = 128K /* sector 1, 128K */ FLASH_TEXT (rx) : ORIGIN = 0x08040000, LENGTH = 1792K /* sectors 6*128 + 8*128 */ DTCM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K /* Used for storage cache */ - RAM (xrw) : ORIGIN = 0x24000000, LENGTH = 512K /* AXI SRAM */ + RAM (xrw) : ORIGIN = 0x24000000, LENGTH = 512K /* AXI SRAM */ + RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 288K } /* produce a link error if there is not this amount of RAM for these sections */ @@ -27,3 +28,12 @@ _ram_start = ORIGIN(RAM); _ram_end = ORIGIN(RAM) + LENGTH(RAM); _heap_start = _ebss; /* heap starts just after statically allocated memory */ _heap_end = _sstack; + +/* Define output sections */ +SECTIONS +{ + .eth_buffers (NOLOAD) : { + . = ABSOLUTE(0x30040000); + *eth.o*(.bss.eth_dma) + } >RAM_D2 +} From d986b2012289cecba1f249c4e29446a0bfe680a2 Mon Sep 17 00:00:00 2001 From: Reinhard Feger Date: Fri, 24 Jul 2020 23:08:41 +0200 Subject: [PATCH 146/179] stm32/eth: Add support for H7 processors. --- ports/stm32/eth.c | 231 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 226 insertions(+), 5 deletions(-) diff --git a/ports/stm32/eth.c b/ports/stm32/eth.c index 33d20707e21c0..7a08e8c28c34d 100644 --- a/ports/stm32/eth.c +++ b/ports/stm32/eth.c @@ -61,7 +61,19 @@ #define PHY_SCSR_SPEED_100FULL (6 << PHY_SCSR_SPEED_Pos) // ETH DMA RX and TX descriptor definitions - +#if defined(STM32H7) +#define RX_DESCR_3_OWN_Pos (31) +#define RX_DESCR_3_IOC_Pos (30) +#define RX_DESCR_3_BUF1V_Pos (24) +#define RX_DESCR_3_PL_Msk (0x7fff) + +#define TX_DESCR_3_OWN_Pos (31) +#define TX_DESCR_3_LD_Pos (29) +#define TX_DESCR_3_FD_Pos (28) +#define TX_DESCR_3_CIC_Pos (16) +#define TX_DESCR_2_B1L_Pos (0) +#define TX_DESCR_2_B1L_Msk (0x3fff << TX_DESCR_2_B1L_Pos) +#else #define RX_DESCR_0_OWN_Pos (31) #define RX_DESCR_0_FL_Pos (16) #define RX_DESCR_0_FL_Msk (0x3fff << RX_DESCR_0_FL_Pos) @@ -78,6 +90,7 @@ #define TX_DESCR_0_TER_Pos (21) #define TX_DESCR_0_TCH_Pos (20) #define TX_DESCR_1_TBS1_Pos (0) +#endif // Configuration values @@ -121,6 +134,20 @@ STATIC void eth_mac_deinit(eth_t *self); STATIC void eth_process_frame(eth_t *self, size_t len, const uint8_t *buf); STATIC void eth_phy_write(uint32_t reg, uint32_t val) { + #if defined(STM32H7) + while (ETH->MACMDIOAR & ETH_MACMDIOAR_MB) { + } + uint32_t ar = ETH->MACMDIOAR; + ar &= ~ETH_MACMDIOAR_RDA_Msk; + ar |= reg << ETH_MACMDIOAR_RDA_Pos; + ar &= ~ETH_MACMDIOAR_MOC_Msk; + ar |= ETH_MACMDIOAR_MOC_WR; + ar |= ETH_MACMDIOAR_MB; + ETH->MACMDIODR = val; + ETH->MACMDIOAR = ar; + while (ETH->MACMDIOAR & ETH_MACMDIOAR_MB) { + } + #else while (ETH->MACMIIAR & ETH_MACMIIAR_MB) { } ETH->MACMIIDR = val; @@ -129,9 +156,24 @@ STATIC void eth_phy_write(uint32_t reg, uint32_t val) { ETH->MACMIIAR = ar; while (ETH->MACMIIAR & ETH_MACMIIAR_MB) { } + #endif } STATIC uint32_t eth_phy_read(uint32_t reg) { + #if defined(STM32H7) + while (ETH->MACMDIOAR & ETH_MACMDIOAR_MB) { + } + uint32_t ar = ETH->MACMDIOAR; + ar &= ~ETH_MACMDIOAR_RDA_Msk; + ar |= reg << ETH_MACMDIOAR_RDA_Pos; + ar &= ~ETH_MACMDIOAR_MOC_Msk; + ar |= ETH_MACMDIOAR_MOC_RD; + ar |= ETH_MACMDIOAR_MB; + ETH->MACMDIOAR = ar; + while (ETH->MACMDIOAR & ETH_MACMDIOAR_MB) { + } + return ETH->MACMDIODR; + #else while (ETH->MACMIIAR & ETH_MACMIIAR_MB) { } uint32_t ar = ETH->MACMIIAR; @@ -140,6 +182,7 @@ STATIC uint32_t eth_phy_read(uint32_t reg) { while (ETH->MACMIIAR & ETH_MACMIIAR_MB) { } return ETH->MACMIIDR; + #endif } void eth_init(eth_t *self, int mac_idx) { @@ -160,7 +203,7 @@ STATIC int eth_mac_init(eth_t *self) { // Configure GPIO mp_hal_pin_config_alt_static(MICROPY_HW_ETH_MDC, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_MDC); mp_hal_pin_config_alt_static(MICROPY_HW_ETH_MDIO, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_MDIO); - mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_REF_CLK, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_RMII_REF_CLK); + mp_hal_pin_config_alt_static_speed(MICROPY_HW_ETH_RMII_REF_CLK, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_MEDIUM, STATIC_AF_ETH_RMII_REF_CLK); mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_CRS_DV, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_RMII_CRS_DV); mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_RXD0, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_RMII_RXD0); mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_RXD1, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_RMII_RXD1); @@ -168,26 +211,53 @@ STATIC int eth_mac_init(eth_t *self) { mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_TXD0, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_RMII_TXD0); mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_TXD1, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_RMII_TXD1); + #if defined(STM32H7) + __HAL_RCC_ETH1MAC_CLK_ENABLE(); + __HAL_RCC_ETH1TX_CLK_ENABLE(); + __HAL_RCC_ETH1RX_CLK_ENABLE(); + __HAL_RCC_ETH1MAC_FORCE_RESET(); + #else __HAL_RCC_ETH_CLK_ENABLE(); __HAL_RCC_ETHMAC_FORCE_RESET(); + #endif // Select RMII interface + #if defined(STM32H7) + SYSCFG->PMCR = (SYSCFG->PMCR & ~SYSCFG_PMCR_EPIS_SEL_Msk) | SYSCFG_PMCR_EPIS_SEL_2; + #else __HAL_RCC_SYSCFG_CLK_ENABLE(); SYSCFG->PMC |= SYSCFG_PMC_MII_RMII_SEL; + #endif + + #if defined(STM32H7) + __HAL_RCC_ETH1MAC_RELEASE_RESET(); + __HAL_RCC_ETH1MAC_CLK_SLEEP_ENABLE(); + __HAL_RCC_ETH1TX_CLK_SLEEP_ENABLE(); + __HAL_RCC_ETH1RX_CLK_SLEEP_ENABLE(); + #else __HAL_RCC_ETHMAC_RELEASE_RESET(); __HAL_RCC_ETHMAC_CLK_SLEEP_ENABLE(); __HAL_RCC_ETHMACTX_CLK_SLEEP_ENABLE(); __HAL_RCC_ETHMACRX_CLK_SLEEP_ENABLE(); + #endif // Do a soft reset of the MAC core - ETH->DMABMR = ETH_DMABMR_SR; + #if defined(STM32H7) + #define ETH_SOFT_RESET(eth) do { eth->DMAMR = ETH_DMAMR_SWR; } while (0) + #define ETH_IS_RESET(eth) (eth->DMAMR & ETH_DMAMR_SWR) + #else + #define ETH_SOFT_RESET(eth) do { eth->DMABMR = ETH_DMABMR_SR; } while (0) + #define ETH_IS_RESET(eth) (eth->DMABMR & ETH_DMABMR_SR) + #endif + + ETH_SOFT_RESET(ETH); mp_hal_delay_ms(2); // Wait for soft reset to finish uint32_t t0 = mp_hal_ticks_ms(); - while (ETH->DMABMR & ETH_DMABMR_SR) { + while (ETH_IS_RESET(ETH)) { if (mp_hal_ticks_ms() - t0 > 1000) { return -MP_ETIMEDOUT; } @@ -196,6 +266,21 @@ STATIC int eth_mac_init(eth_t *self) { // Set MII clock range uint32_t hclk = HAL_RCC_GetHCLKFreq(); uint32_t cr_div; + #if defined(STM32H7) + cr_div = ETH->MACMDIOAR & ~ETH_MACMDIOAR_CR; + if (hclk < 35000000) { + cr_div |= ETH_MACMDIOAR_CR_DIV16; + } else if (hclk < 60000000) { + cr_div |= ETH_MACMDIOAR_CR_DIV26; + } else if (hclk < 100000000) { + cr_div |= ETH_MACMDIOAR_CR_DIV42; + } else if (hclk < 150000000) { + cr_div |= ETH_MACMDIOAR_CR_DIV62; + } else { + cr_div |= ETH_MACMDIOAR_CR_DIV102; + } + ETH->MACMDIOAR = cr_div; + #else if (hclk < 35000000) { cr_div = ETH_MACMIIAR_CR_Div16; } else if (hclk < 60000000) { @@ -208,6 +293,12 @@ STATIC int eth_mac_init(eth_t *self) { cr_div = ETH_MACMIIAR_CR_Div102; } ETH->MACMIIAR = cr_div; + #endif + + #if defined(STM32H7) + // don't skip 32bit words since our desriptors are continuous in memory + ETH->DMACCR &= ~(ETH_DMACCR_DSL_Msk); + #endif // Reset the PHY eth_phy_write(PHY_BCR, PHY_BCR_SOFT_RESET); @@ -249,17 +340,36 @@ STATIC int eth_mac_init(eth_t *self) { uint16_t phy_scsr = eth_phy_read(PHY_SCSR); // Burst mode configuration + #if defined(STM32H7) + ETH->DMASBMR = ETH->DMASBMR & ~ETH_DMASBMR_AAL & ~ETH_DMASBMR_FB; + #else ETH->DMABMR = 0; + #endif mp_hal_delay_ms(2); // Select DMA interrupts + #if defined(STM32H7) + ETH->DMACIER = ETH->DMACIER + | ETH_DMACIER_NIE // enable normal interrupts + | ETH_DMACIER_RIE // enable RX interrupt + ; + #else ETH->DMAIER = ETH_DMAIER_NISE // enable normal interrupts | ETH_DMAIER_RIE // enable RX interrupt ; + #endif // Configure RX descriptor lists for (size_t i = 0; i < RX_BUF_NUM; ++i) { + #if defined(STM32H7) + eth_dma.rx_descr[i].rdes3 = + 1 << RX_DESCR_3_OWN_Pos + | (1 << RX_DESCR_3_BUF1V_Pos) // buf1 address valid + | (1 << RX_DESCR_3_IOC_Pos) // Interrupt Enabled on Completion + ; + eth_dma.rx_descr[i].rdes0 = (uint32_t)ð_dma.rx_buf[i * RX_BUF_SIZE]; // buf 1 address + #else eth_dma.rx_descr[i].rdes0 = 1 << RX_DESCR_0_OWN_Pos; eth_dma.rx_descr[i].rdes1 = 1 << RX_DESCR_1_RCH_Pos // chained @@ -267,31 +377,64 @@ STATIC int eth_mac_init(eth_t *self) { ; eth_dma.rx_descr[i].rdes2 = (uint32_t)ð_dma.rx_buf[i * RX_BUF_SIZE]; eth_dma.rx_descr[i].rdes3 = (uint32_t)ð_dma.rx_descr[(i + 1) % RX_BUF_NUM]; + #endif } + + #if defined(STM32H7) + ETH->DMACRDLAR = (uint32_t)ð_dma.rx_descr[0]; + #else ETH->DMARDLAR = (uint32_t)ð_dma.rx_descr[0]; + #endif eth_dma.rx_descr_idx = 0; // Configure TX descriptor lists for (size_t i = 0; i < TX_BUF_NUM; ++i) { + #if defined(STM32H7) + eth_dma.tx_descr[i].tdes0 = 0; + eth_dma.tx_descr[i].tdes1 = 0; + eth_dma.tx_descr[i].tdes2 = TX_BUF_SIZE & TX_DESCR_2_B1L_Msk; + eth_dma.tx_descr[i].tdes3 = 0; + #else eth_dma.tx_descr[i].tdes0 = 1 << TX_DESCR_0_TCH_Pos; eth_dma.tx_descr[i].tdes1 = 0; eth_dma.tx_descr[i].tdes2 = 0; eth_dma.tx_descr[i].tdes3 = (uint32_t)ð_dma.tx_descr[(i + 1) % TX_BUF_NUM]; + #endif } + + #if defined(STM32H7) + // set number of descriptors and buffers + ETH->DMACTDRLR = TX_BUF_NUM - 1; + ETH->DMACRDRLR = RX_BUF_NUM - 1; + + ETH->DMACTDLAR = (uint32_t)ð_dma.tx_descr[0]; + #else ETH->DMATDLAR = (uint32_t)ð_dma.tx_descr[0]; + #endif eth_dma.tx_descr_idx = 0; // Configure DMA + #if defined(STM32H7) + // read from RX FIFO only after a full frame is written + ETH->MTLRQOMR = ETH_MTLRQOMR_RSF; + // transmission starts when a full packet resides in the Tx queue + ETH->MTLTQOMR = ETH_MTLTQOMR_TSF; + #else ETH->DMAOMR = ETH_DMAOMR_RSF // read from RX FIFO after a full frame is written | ETH_DMAOMR_TSF // transmit when a full frame is in TX FIFO (needed by errata) ; + #endif mp_hal_delay_ms(2); // Select MAC filtering options + #if defined(STM32H7) + ETH->MACPFR = ETH_MACPFR_RA; // pass all frames up + #else ETH->MACFFR = ETH_MACFFR_RA // pass all frames up ; + #endif mp_hal_delay_ms(2); // Set MAC address @@ -318,10 +461,15 @@ STATIC int eth_mac_init(eth_t *self) { mp_hal_delay_ms(2); // Start DMA layer + #if defined(STM32H7) + ETH->DMACRCR |= ETH_DMACRCR_SR; // start RX + ETH->DMACTCR |= ETH_DMACTCR_ST; // start TX + #else ETH->DMAOMR |= ETH_DMAOMR_ST // start TX | ETH_DMAOMR_SR // start RX ; + #endif mp_hal_delay_ms(2); // Enable interrupts @@ -334,9 +482,15 @@ STATIC int eth_mac_init(eth_t *self) { STATIC void eth_mac_deinit(eth_t *self) { (void)self; HAL_NVIC_DisableIRQ(ETH_IRQn); + #if defined(STM32H7) + __HAL_RCC_ETH1MAC_FORCE_RESET(); + __HAL_RCC_ETH1MAC_RELEASE_RESET(); + __HAL_RCC_ETH1MAC_CLK_DISABLE(); + #else __HAL_RCC_ETHMAC_FORCE_RESET(); __HAL_RCC_ETHMAC_RELEASE_RESET(); __HAL_RCC_ETH_CLK_DISABLE(); + #endif } STATIC int eth_tx_buf_get(size_t len, uint8_t **buf) { @@ -348,19 +502,32 @@ STATIC int eth_tx_buf_get(size_t len, uint8_t **buf) { eth_dma_tx_descr_t *tx_descr = ð_dma.tx_descr[eth_dma.tx_descr_idx]; uint32_t t0 = mp_hal_ticks_ms(); for (;;) { + #if defined(STM32H7) + if (!(tx_descr->tdes3 & (1 << TX_DESCR_3_OWN_Pos))) { + break; + } + #else if (!(tx_descr->tdes0 & (1 << TX_DESCR_0_OWN_Pos))) { break; } + #endif if (mp_hal_ticks_ms() - t0 > 1000) { return -MP_ETIMEDOUT; } } + #if defined(STM32H7) + // Update TX descriptor with length and buffer pointer + *buf = ð_dma.tx_buf[eth_dma.tx_descr_idx * TX_BUF_SIZE]; + tx_descr->tdes2 = len & TX_DESCR_2_B1L_Msk; + tx_descr->tdes0 = (uint32_t)*buf; + #else // Update TX descriptor with length, buffer pointer and linked list pointer *buf = ð_dma.tx_buf[eth_dma.tx_descr_idx * TX_BUF_SIZE]; tx_descr->tdes1 = len << TX_DESCR_1_TBS1_Pos; tx_descr->tdes2 = (uint32_t)*buf; tx_descr->tdes3 = (uint32_t)ð_dma.tx_descr[(eth_dma.tx_descr_idx + 1) % TX_BUF_NUM]; + #endif return 0; } @@ -371,6 +538,14 @@ STATIC int eth_tx_buf_send(void) { eth_dma.tx_descr_idx = (eth_dma.tx_descr_idx + 1) % TX_BUF_NUM; // Schedule to send next outgoing frame + #if defined(STM32H7) + tx_descr->tdes3 = + 1 << TX_DESCR_3_OWN_Pos // owned by DMA + | 1 << TX_DESCR_3_LD_Pos // last segment + | 1 << TX_DESCR_3_FD_Pos // first segment + | 3 << TX_DESCR_3_CIC_Pos // enable all checksums inserted by hardware + ; + #else tx_descr->tdes0 = 1 << TX_DESCR_0_OWN_Pos // owned by DMA | 1 << TX_DESCR_0_LS_Pos // last segment @@ -378,13 +553,21 @@ STATIC int eth_tx_buf_send(void) { | 3 << TX_DESCR_0_CIC_Pos // enable all checksums inserted by hardware | 1 << TX_DESCR_0_TCH_Pos // TX descriptor is chained ; + #endif // Notify ETH DMA that there is a new TX descriptor for sending __DMB(); + #if defined(STM32H7) + if (ETH->DMACSR & ETH_DMACSR_TBU) { + ETH->DMACSR = ETH_DMACSR_TBU; + } + ETH->DMACTDTPR = (uint32_t)ð_dma.tx_descr[eth_dma.tx_descr_idx]; + #else if (ETH->DMASR & ETH_DMASR_TBUS) { ETH->DMASR = ETH_DMASR_TBUS; ETH->DMATPDR = 0; } + #endif return 0; } @@ -396,6 +579,12 @@ STATIC void eth_dma_rx_free(void) { eth_dma.rx_descr_idx = (eth_dma.rx_descr_idx + 1) % RX_BUF_NUM; // Schedule to get next incoming frame + #if defined(STM32H7) + rx_descr->rdes0 = (uint32_t)buf; + rx_descr->rdes3 = 1 << RX_DESCR_3_OWN_Pos; // owned by DMA + rx_descr->rdes3 |= 1 << RX_DESCR_3_BUF1V_Pos; // buf 1 address valid + rx_descr->rdes3 |= 1 << RX_DESCR_3_IOC_Pos; // Interrupt Enabled on Completion + #else rx_descr->rdes1 = 1 << RX_DESCR_1_RCH_Pos // RX descriptor is chained | RX_BUF_SIZE << RX_DESCR_1_RBS1_Pos // maximum buffer length @@ -403,28 +592,60 @@ STATIC void eth_dma_rx_free(void) { rx_descr->rdes2 = (uint32_t)buf; rx_descr->rdes3 = (uint32_t)ð_dma.rx_descr[eth_dma.rx_descr_idx]; rx_descr->rdes0 = 1 << RX_DESCR_0_OWN_Pos; // owned by DMA + #endif // Notify ETH DMA that there is a new RX descriptor available __DMB(); + #if defined(STM32H7) + ETH->DMACRDTPR = (uint32_t)&rx_descr[eth_dma.rx_descr_idx]; + #else ETH->DMARPDR = 0; + #endif } void ETH_IRQHandler(void) { + #if defined(STM32H7) + uint32_t sr = ETH->DMACSR; + ETH->DMACSR = ETH_DMACSR_NIS; + uint32_t rx_interrupt = sr & ETH_DMACSR_RI; + #else uint32_t sr = ETH->DMASR; ETH->DMASR = ETH_DMASR_NIS; - if (sr & ETH_DMASR_RS) { + uint32_t rx_interrupt = sr & ETH_DMASR_RS; + #endif + if (rx_interrupt) { + #if defined(STM32H7) + ETH->DMACSR = ETH_DMACSR_RI; + #else ETH->DMASR = ETH_DMASR_RS; + #endif for (;;) { + #if defined(STM32H7) + eth_dma_rx_descr_t *rx_descr_l = ð_dma.rx_descr[eth_dma.rx_descr_idx]; + if (rx_descr_l->rdes3 & (1 << RX_DESCR_3_OWN_Pos)) { + // No more RX descriptors ready to read + break; + } + #else eth_dma_rx_descr_t *rx_descr = ð_dma.rx_descr[eth_dma.rx_descr_idx]; if (rx_descr->rdes0 & (1 << RX_DESCR_0_OWN_Pos)) { // No more RX descriptors ready to read break; } + #endif // Get RX buffer containing new frame + #if defined(STM32H7) + size_t len = (rx_descr_l->rdes3 & RX_DESCR_3_PL_Msk); + #else size_t len = (rx_descr->rdes0 & RX_DESCR_0_FL_Msk) >> RX_DESCR_0_FL_Pos; + #endif len -= 4; // discard CRC at end + #if defined(STM32H7) + uint8_t *buf = ð_dma.rx_buf[eth_dma.rx_descr_idx * RX_BUF_SIZE]; + #else uint8_t *buf = (uint8_t *)rx_descr->rdes2; + #endif // Process frame eth_process_frame(ð_instance, len, buf); From 032e0956200d602dcd1dbee8b1894f2e3227e779 Mon Sep 17 00:00:00 2001 From: Reinhard Feger Date: Fri, 24 Jul 2020 23:13:17 +0200 Subject: [PATCH 147/179] stm32/boards/NUCLEO_H743ZI: Enable ethernet peripheral. --- ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.h b/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.h index 4e6b162269a8a..324f2b03ce0d0 100644 --- a/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_H743ZI/mpconfigboard.h @@ -91,8 +91,8 @@ void NUCLEO_H743ZI_board_early_init(void); #define MICROPY_HW_SDCARD_DETECT_PULL (GPIO_PULLUP) #define MICROPY_HW_SDCARD_DETECT_PRESENT (GPIO_PIN_RESET) -// Ethernet via RMII (MDC define disabled for now until eth.c supports H7) -//#define MICROPY_HW_ETH_MDC (pin_C1) +// Ethernet via RMII +#define MICROPY_HW_ETH_MDC (pin_C1) #define MICROPY_HW_ETH_MDIO (pin_A2) #define MICROPY_HW_ETH_RMII_REF_CLK (pin_A1) #define MICROPY_HW_ETH_RMII_CRS_DV (pin_A7) From d9d761b0572b9b99e97f1b5dd39226173f2e239e Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 9 Dec 2020 11:01:24 +1100 Subject: [PATCH 148/179] lib/littlefs: Update littlefs2 to v2.3.0. At commit 1a59954ec64ca168828a15242cc6de94ac75f9d1 Signed-off-by: Damien George --- lib/littlefs/README.md | 4 +- lib/littlefs/lfs2.c | 1169 ++++++++++++++++++++++++++++------------ lib/littlefs/lfs2.h | 40 +- 3 files changed, 862 insertions(+), 351 deletions(-) diff --git a/lib/littlefs/README.md b/lib/littlefs/README.md index 1f0aacbf9840f..ca21a05f42891 100644 --- a/lib/littlefs/README.md +++ b/lib/littlefs/README.md @@ -2,7 +2,7 @@ littlefs library ================ The upstream source for the files in this directory is -https://github.com/ARMmbed/littlefs +https://github.com/littlefs-project/littlefs To generate the separate files with lfs1 and lfs2 prefixes run the following commands in the top-level directory of the littlefs repository (replace the @@ -13,7 +13,7 @@ version tags with the latest/desired ones, and set `$MPY_DIR`): cp lfs1*.[ch] $MPY_DIR/lib/littlefs git reset --hard HEAD - git checkout v2.1.3 + git checkout v2.3.0 python2 ./scripts/prefix.py lfs2 cp lfs2*.[ch] $MPY_DIR/lib/littlefs git reset --hard HEAD diff --git a/lib/littlefs/lfs2.c b/lib/littlefs/lfs2.c index 6bae806ceb41c..31f3c603e7b87 100644 --- a/lib/littlefs/lfs2.c +++ b/lib/littlefs/lfs2.c @@ -118,24 +118,29 @@ static int lfs2_bd_cmp(lfs2_t *lfs2, lfs2_block_t block, lfs2_off_t off, const void *buffer, lfs2_size_t size) { const uint8_t *data = buffer; + lfs2_size_t diff = 0; - for (lfs2_off_t i = 0; i < size; i++) { - uint8_t dat; - int err = lfs2_bd_read(lfs2, + for (lfs2_off_t i = 0; i < size; i += diff) { + uint8_t dat[8]; + + diff = lfs2_min(size-i, sizeof(dat)); + int res = lfs2_bd_read(lfs2, pcache, rcache, hint-i, - block, off+i, &dat, 1); - if (err) { - return err; + block, off+i, &dat, diff); + if (res) { + return res; } - if (dat != data[i]) { - return (dat < data[i]) ? LFS2_CMP_LT : LFS2_CMP_GT; + res = memcmp(dat, data + i, diff); + if (res) { + return res < 0 ? LFS2_CMP_LT : LFS2_CMP_GT; } } return LFS2_CMP_EQ; } +#ifndef LFS2_READONLY static int lfs2_bd_flush(lfs2_t *lfs2, lfs2_cache_t *pcache, lfs2_cache_t *rcache, bool validate) { if (pcache->block != LFS2_BLOCK_NULL && pcache->block != LFS2_BLOCK_INLINE) { @@ -168,7 +173,9 @@ static int lfs2_bd_flush(lfs2_t *lfs2, return 0; } +#endif +#ifndef LFS2_READONLY static int lfs2_bd_sync(lfs2_t *lfs2, lfs2_cache_t *pcache, lfs2_cache_t *rcache, bool validate) { lfs2_cache_drop(lfs2, rcache); @@ -182,7 +189,9 @@ static int lfs2_bd_sync(lfs2_t *lfs2, LFS2_ASSERT(err <= 0); return err; } +#endif +#ifndef LFS2_READONLY static int lfs2_bd_prog(lfs2_t *lfs2, lfs2_cache_t *pcache, lfs2_cache_t *rcache, bool validate, lfs2_block_t block, lfs2_off_t off, @@ -228,13 +237,16 @@ static int lfs2_bd_prog(lfs2_t *lfs2, return 0; } +#endif +#ifndef LFS2_READONLY static int lfs2_bd_erase(lfs2_t *lfs2, lfs2_block_t block) { LFS2_ASSERT(block < lfs2->cfg->block_count); int err = lfs2->cfg->erase(lfs2->cfg, block); LFS2_ASSERT(err <= 0); return err; } +#endif /// Small type-level utilities /// @@ -388,10 +400,12 @@ static void lfs2_ctz_fromle32(struct lfs2_ctz *ctz) { ctz->size = lfs2_fromle32(ctz->size); } +#ifndef LFS2_READONLY static void lfs2_ctz_tole32(struct lfs2_ctz *ctz) { ctz->head = lfs2_tole32(ctz->head); ctz->size = lfs2_tole32(ctz->size); } +#endif static inline void lfs2_superblock_fromle32(lfs2_superblock_t *superblock) { superblock->version = lfs2_fromle32(superblock->version); @@ -411,15 +425,46 @@ static inline void lfs2_superblock_tole32(lfs2_superblock_t *superblock) { superblock->attr_max = lfs2_tole32(superblock->attr_max); } +static inline bool lfs2_mlist_isopen(struct lfs2_mlist *head, + struct lfs2_mlist *node) { + for (struct lfs2_mlist **p = &head; *p; p = &(*p)->next) { + if (*p == (struct lfs2_mlist*)node) { + return true; + } + } + + return false; +} + +static inline void lfs2_mlist_remove(lfs2_t *lfs2, struct lfs2_mlist *mlist) { + for (struct lfs2_mlist **p = &lfs2->mlist; *p; p = &(*p)->next) { + if (*p == mlist) { + *p = (*p)->next; + break; + } + } +} + +static inline void lfs2_mlist_append(lfs2_t *lfs2, struct lfs2_mlist *mlist) { + mlist->next = lfs2->mlist; + lfs2->mlist = mlist; +} + /// Internal operations predeclared here /// +#ifndef LFS2_READONLY static int lfs2_dir_commit(lfs2_t *lfs2, lfs2_mdir_t *dir, const struct lfs2_mattr *attrs, int attrcount); static int lfs2_dir_compact(lfs2_t *lfs2, lfs2_mdir_t *dir, const struct lfs2_mattr *attrs, int attrcount, lfs2_mdir_t *source, uint16_t begin, uint16_t end); + +static lfs2_ssize_t lfs2_file_rawwrite(lfs2_t *lfs2, lfs2_file_t *file, + const void *buffer, lfs2_size_t size); +static int lfs2_file_rawsync(lfs2_t *lfs2, lfs2_file_t *file); static int lfs2_file_outline(lfs2_t *lfs2, lfs2_file_t *file); static int lfs2_file_flush(lfs2_t *lfs2, lfs2_file_t *file); + static void lfs2_fs_preporphans(lfs2_t *lfs2, int8_t orphans); static void lfs2_fs_prepmove(lfs2_t *lfs2, uint16_t id, const lfs2_block_t pair[2]); @@ -429,17 +474,32 @@ static lfs2_stag_t lfs2_fs_parent(lfs2_t *lfs2, const lfs2_block_t dir[2], lfs2_mdir_t *parent); static int lfs2_fs_relocate(lfs2_t *lfs2, const lfs2_block_t oldpair[2], lfs2_block_t newpair[2]); -int lfs2_fs_traverseraw(lfs2_t *lfs2, - int (*cb)(void *data, lfs2_block_t block), void *data, - bool includeorphans); static int lfs2_fs_forceconsistency(lfs2_t *lfs2); -static int lfs2_deinit(lfs2_t *lfs2); +#endif + #ifdef LFS2_MIGRATE static int lfs21_traverse(lfs2_t *lfs2, int (*cb)(void*, lfs2_block_t), void *data); #endif +static int lfs2_dir_rawrewind(lfs2_t *lfs2, lfs2_dir_t *dir); + +static lfs2_ssize_t lfs2_file_rawread(lfs2_t *lfs2, lfs2_file_t *file, + void *buffer, lfs2_size_t size); +static int lfs2_file_rawclose(lfs2_t *lfs2, lfs2_file_t *file); +static lfs2_soff_t lfs2_file_rawsize(lfs2_t *lfs2, lfs2_file_t *file); + +static lfs2_ssize_t lfs2_fs_rawsize(lfs2_t *lfs2); +static int lfs2_fs_rawtraverse(lfs2_t *lfs2, + int (*cb)(void *data, lfs2_block_t block), void *data, + bool includeorphans); + +static int lfs2_deinit(lfs2_t *lfs2); +static int lfs2_rawunmount(lfs2_t *lfs2); + + /// Block allocator /// +#ifndef LFS2_READONLY static int lfs2_alloc_lookahead(void *p, lfs2_block_t block) { lfs2_t *lfs2 = (lfs2_t*)p; lfs2_block_t off = ((block - lfs2->free.off) @@ -451,20 +511,24 @@ static int lfs2_alloc_lookahead(void *p, lfs2_block_t block) { return 0; } +#endif +// indicate allocated blocks have been committed into the filesystem, this +// is to prevent blocks from being garbage collected in the middle of a +// commit operation static void lfs2_alloc_ack(lfs2_t *lfs2) { lfs2->free.ack = lfs2->cfg->block_count; } -// Invalidate the lookahead buffer. This is done during mounting and -// failed traversals -static void lfs2_alloc_reset(lfs2_t *lfs2) { - lfs2->free.off = lfs2->seed % lfs2->cfg->block_size; +// drop the lookahead buffer, this is done during mounting and failed +// traversals in order to avoid invalid lookahead state +static void lfs2_alloc_drop(lfs2_t *lfs2) { lfs2->free.size = 0; lfs2->free.i = 0; lfs2_alloc_ack(lfs2); } +#ifndef LFS2_READONLY static int lfs2_alloc(lfs2_t *lfs2, lfs2_block_t *block) { while (true) { while (lfs2->free.i != lfs2->free.size) { @@ -503,13 +567,14 @@ static int lfs2_alloc(lfs2_t *lfs2, lfs2_block_t *block) { // find mask of free blocks from tree memset(lfs2->free.buffer, 0, lfs2->cfg->lookahead_size); - int err = lfs2_fs_traverseraw(lfs2, lfs2_alloc_lookahead, lfs2, true); + int err = lfs2_fs_rawtraverse(lfs2, lfs2_alloc_lookahead, lfs2, true); if (err) { - lfs2_alloc_reset(lfs2); + lfs2_alloc_drop(lfs2); return err; } } } +#endif /// Metadata pair and directory operations /// static lfs2_stag_t lfs2_dir_getslice(lfs2_t *lfs2, const lfs2_mdir_t *dir, @@ -642,6 +707,7 @@ static int lfs2_dir_getread(lfs2_t *lfs2, const lfs2_mdir_t *dir, return 0; } +#ifndef LFS2_READONLY static int lfs2_dir_traverse_filter(void *p, lfs2_tag_t tag, const void *buffer) { lfs2_tag_t *filtertag = p; @@ -669,7 +735,9 @@ static int lfs2_dir_traverse_filter(void *p, return false; } +#endif +#ifndef LFS2_READONLY static int lfs2_dir_traverse(lfs2_t *lfs2, const lfs2_mdir_t *dir, lfs2_off_t off, lfs2_tag_t ptag, const struct lfs2_mattr *attrs, int attrcount, @@ -763,6 +831,7 @@ static int lfs2_dir_traverse(lfs2_t *lfs2, } } } +#endif static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, lfs2_mdir_t *dir, const lfs2_block_t pair[2], @@ -870,8 +939,10 @@ static lfs2_stag_t lfs2_dir_fetchmatch(lfs2_t *lfs2, ptag ^= (lfs2_tag_t)(lfs2_tag_chunk(tag) & 1U) << 31; // toss our crc into the filesystem seed for - // pseudorandom numbers - lfs2->seed ^= crc; + // pseudorandom numbers, note we use another crc here + // as a collection function because it is sufficiently + // random and convenient + lfs2->seed = lfs2_crc(lfs2->seed, &crc, sizeof(crc)); // update with what's found so far besttag = tempbesttag; @@ -1200,6 +1271,7 @@ struct lfs2_commit { lfs2_off_t end; }; +#ifndef LFS2_READONLY static int lfs2_dir_commitprog(lfs2_t *lfs2, struct lfs2_commit *commit, const void *buffer, lfs2_size_t size) { int err = lfs2_bd_prog(lfs2, @@ -1214,7 +1286,9 @@ static int lfs2_dir_commitprog(lfs2_t *lfs2, struct lfs2_commit *commit, commit->off += size; return 0; } +#endif +#ifndef LFS2_READONLY static int lfs2_dir_commitattr(lfs2_t *lfs2, struct lfs2_commit *commit, lfs2_tag_t tag, const void *buffer) { // check if we fit @@ -1259,14 +1333,17 @@ static int lfs2_dir_commitattr(lfs2_t *lfs2, struct lfs2_commit *commit, commit->ptag = tag & 0x7fffffff; return 0; } +#endif +#ifndef LFS2_READONLY static int lfs2_dir_commitcrc(lfs2_t *lfs2, struct lfs2_commit *commit) { - const lfs2_off_t off1 = commit->off; - const uint32_t crc1 = commit->crc; // align to program units - const lfs2_off_t end = lfs2_alignup(off1 + 2*sizeof(uint32_t), + const lfs2_off_t end = lfs2_alignup(commit->off + 2*sizeof(uint32_t), lfs2->cfg->prog_size); + lfs2_off_t off1 = 0; + uint32_t crc1 = 0; + // create crc tags to fill up remainder of commit, note that // padding is not crced, which lets fetches skip padding but // makes committing a bit more complicated @@ -1302,6 +1379,12 @@ static int lfs2_dir_commitcrc(lfs2_t *lfs2, struct lfs2_commit *commit) { return err; } + // keep track of non-padding checksum to verify + if (off1 == 0) { + off1 = commit->off + sizeof(uint32_t); + crc1 = commit->crc; + } + commit->off += sizeof(tag)+lfs2_tag_size(tag); commit->ptag = tag ^ ((lfs2_tag_t)reset << 31); commit->crc = 0xffffffff; // reset crc for next "commit" @@ -1315,7 +1398,7 @@ static int lfs2_dir_commitcrc(lfs2_t *lfs2, struct lfs2_commit *commit) { // successful commit, check checksums to make sure lfs2_off_t off = commit->begin; - lfs2_off_t noff = off1 + sizeof(uint32_t); + lfs2_off_t noff = off1; while (off < end) { uint32_t crc = 0xffffffff; for (lfs2_off_t i = off; i < noff+sizeof(uint32_t); i++) { @@ -1352,7 +1435,9 @@ static int lfs2_dir_commitcrc(lfs2_t *lfs2, struct lfs2_commit *commit) { return 0; } +#endif +#ifndef LFS2_READONLY static int lfs2_dir_alloc(lfs2_t *lfs2, lfs2_mdir_t *dir) { // allocate pair of dir blocks (backwards, so we write block 1 first) for (int i = 0; i < 2; i++) { @@ -1375,8 +1460,12 @@ static int lfs2_dir_alloc(lfs2_t *lfs2, lfs2_mdir_t *dir) { return err; } - // make sure we don't immediately evict - dir->rev += dir->rev & 1; + // to make sure we don't immediately evict, align the new revision count + // to our block_cycles modulus, see lfs2_dir_compact for why our modulus + // is tweaked this way + if (lfs2->cfg->block_cycles > 0) { + dir->rev = lfs2_alignup(dir->rev, ((lfs2->cfg->block_cycles+1)|1)); + } // set defaults dir->off = sizeof(dir->rev); @@ -1390,7 +1479,9 @@ static int lfs2_dir_alloc(lfs2_t *lfs2, lfs2_mdir_t *dir) { // don't write out yet, let caller take care of that return 0; } +#endif +#ifndef LFS2_READONLY static int lfs2_dir_drop(lfs2_t *lfs2, lfs2_mdir_t *dir, lfs2_mdir_t *tail) { // steal state int err = lfs2_dir_getgstate(lfs2, tail, &lfs2->gdelta); @@ -1409,7 +1500,9 @@ static int lfs2_dir_drop(lfs2_t *lfs2, lfs2_mdir_t *dir, lfs2_mdir_t *tail) { return 0; } +#endif +#ifndef LFS2_READONLY static int lfs2_dir_split(lfs2_t *lfs2, lfs2_mdir_t *dir, const struct lfs2_mattr *attrs, int attrcount, lfs2_mdir_t *source, uint16_t split, uint16_t end) { @@ -1442,7 +1535,9 @@ static int lfs2_dir_split(lfs2_t *lfs2, return 0; } +#endif +#ifndef LFS2_READONLY static int lfs2_dir_commit_size(void *p, lfs2_tag_t tag, const void *buffer) { lfs2_size_t *size = p; (void)buffer; @@ -1450,17 +1545,23 @@ static int lfs2_dir_commit_size(void *p, lfs2_tag_t tag, const void *buffer) { *size += lfs2_tag_dsize(tag); return 0; } +#endif +#ifndef LFS2_READONLY struct lfs2_dir_commit_commit { lfs2_t *lfs2; struct lfs2_commit *commit; }; +#endif +#ifndef LFS2_READONLY static int lfs2_dir_commit_commit(void *p, lfs2_tag_t tag, const void *buffer) { struct lfs2_dir_commit_commit *commit = p; return lfs2_dir_commitattr(commit->lfs2, commit->commit, tag, buffer); } +#endif +#ifndef LFS2_READONLY static int lfs2_dir_compact(lfs2_t *lfs2, lfs2_mdir_t *dir, const struct lfs2_mattr *attrs, int attrcount, lfs2_mdir_t *source, uint16_t begin, uint16_t end) { @@ -1525,7 +1626,7 @@ static int lfs2_dir_compact(lfs2_t *lfs2, if (lfs2_pair_cmp(dir->pair, (const lfs2_block_t[2]){0, 1}) == 0) { // oh no! we're writing too much to the superblock, // should we expand? - lfs2_ssize_t res = lfs2_fs_size(lfs2); + lfs2_ssize_t res = lfs2_fs_rawsize(lfs2); if (res < 0) { return res; } @@ -1715,7 +1816,9 @@ static int lfs2_dir_compact(lfs2_t *lfs2, return 0; } +#endif +#ifndef LFS2_READONLY static int lfs2_dir_commit(lfs2_t *lfs2, lfs2_mdir_t *dir, const struct lfs2_mattr *attrs, int attrcount) { // check for any inline files that aren't RAM backed and @@ -1903,15 +2006,15 @@ static int lfs2_dir_commit(lfs2_t *lfs2, lfs2_mdir_t *dir, return 0; } +#endif /// Top level directory operations /// -int lfs2_mkdir(lfs2_t *lfs2, const char *path) { - LFS2_TRACE("lfs2_mkdir(%p, \"%s\")", (void*)lfs2, path); +#ifndef LFS2_READONLY +static int lfs2_rawmkdir(lfs2_t *lfs2, const char *path) { // deorphan if we haven't yet, needed at most once after poweron int err = lfs2_fs_forceconsistency(lfs2); if (err) { - LFS2_TRACE("lfs2_mkdir -> %d", err); return err; } @@ -1920,14 +2023,12 @@ int lfs2_mkdir(lfs2_t *lfs2, const char *path) { uint16_t id; err = lfs2_dir_find(lfs2, &cwd.m, &path, &id); if (!(err == LFS2_ERR_NOENT && id != 0x3ff)) { - LFS2_TRACE("lfs2_mkdir -> %d", (err < 0) ? err : LFS2_ERR_EXIST); return (err < 0) ? err : LFS2_ERR_EXIST; } // check that name fits lfs2_size_t nlen = strlen(path); if (nlen > lfs2->name_max) { - LFS2_TRACE("lfs2_mkdir -> %d", LFS2_ERR_NAMETOOLONG); return LFS2_ERR_NAMETOOLONG; } @@ -1936,7 +2037,6 @@ int lfs2_mkdir(lfs2_t *lfs2, const char *path) { lfs2_mdir_t dir; err = lfs2_dir_alloc(lfs2, &dir); if (err) { - LFS2_TRACE("lfs2_mkdir -> %d", err); return err; } @@ -1945,7 +2045,6 @@ int lfs2_mkdir(lfs2_t *lfs2, const char *path) { while (pred.split) { err = lfs2_dir_fetch(lfs2, &pred, pred.tail); if (err) { - LFS2_TRACE("lfs2_mkdir -> %d", err); return err; } } @@ -1956,7 +2055,6 @@ int lfs2_mkdir(lfs2_t *lfs2, const char *path) { {LFS2_MKTAG(LFS2_TYPE_SOFTTAIL, 0x3ff, 8), pred.tail})); lfs2_pair_fromle32(pred.tail); if (err) { - LFS2_TRACE("lfs2_mkdir -> %d", err); return err; } @@ -1979,7 +2077,6 @@ int lfs2_mkdir(lfs2_t *lfs2, const char *path) { lfs2_pair_fromle32(dir.pair); if (err) { lfs2->mlist = cwd.next; - LFS2_TRACE("lfs2_mkdir -> %d", err); return err; } @@ -1997,24 +2094,20 @@ int lfs2_mkdir(lfs2_t *lfs2, const char *path) { LFS2_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair})); lfs2_pair_fromle32(dir.pair); if (err) { - LFS2_TRACE("lfs2_mkdir -> %d", err); return err; } - LFS2_TRACE("lfs2_mkdir -> %d", 0); return 0; } +#endif -int lfs2_dir_open(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path) { - LFS2_TRACE("lfs2_dir_open(%p, %p, \"%s\")", (void*)lfs2, (void*)dir, path); +static int lfs2_dir_rawopen(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path) { lfs2_stag_t tag = lfs2_dir_find(lfs2, &dir->m, &path, NULL); if (tag < 0) { - LFS2_TRACE("lfs2_dir_open -> %"PRId32, tag); return tag; } if (lfs2_tag_type3(tag) != LFS2_TYPE_DIR) { - LFS2_TRACE("lfs2_dir_open -> %d", LFS2_ERR_NOTDIR); return LFS2_ERR_NOTDIR; } @@ -2028,7 +2121,6 @@ int lfs2_dir_open(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path) { lfs2_stag_t res = lfs2_dir_get(lfs2, &dir->m, LFS2_MKTAG(0x700, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_STRUCT, lfs2_tag_id(tag), 8), pair); if (res < 0) { - LFS2_TRACE("lfs2_dir_open -> %"PRId32, res); return res; } lfs2_pair_fromle32(pair); @@ -2037,7 +2129,6 @@ int lfs2_dir_open(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path) { // fetch first pair int err = lfs2_dir_fetch(lfs2, &dir->m, pair); if (err) { - LFS2_TRACE("lfs2_dir_open -> %d", err); return err; } @@ -2049,30 +2140,19 @@ int lfs2_dir_open(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path) { // add to list of mdirs dir->type = LFS2_TYPE_DIR; - dir->next = (lfs2_dir_t*)lfs2->mlist; - lfs2->mlist = (struct lfs2_mlist*)dir; + lfs2_mlist_append(lfs2, (struct lfs2_mlist *)dir); - LFS2_TRACE("lfs2_dir_open -> %d", 0); return 0; } -int lfs2_dir_close(lfs2_t *lfs2, lfs2_dir_t *dir) { - LFS2_TRACE("lfs2_dir_close(%p, %p)", (void*)lfs2, (void*)dir); +static int lfs2_dir_rawclose(lfs2_t *lfs2, lfs2_dir_t *dir) { // remove from list of mdirs - for (struct lfs2_mlist **p = &lfs2->mlist; *p; p = &(*p)->next) { - if (*p == (struct lfs2_mlist*)dir) { - *p = (*p)->next; - break; - } - } + lfs2_mlist_remove(lfs2, (struct lfs2_mlist *)dir); - LFS2_TRACE("lfs2_dir_close -> %d", 0); return 0; } -int lfs2_dir_read(lfs2_t *lfs2, lfs2_dir_t *dir, struct lfs2_info *info) { - LFS2_TRACE("lfs2_dir_read(%p, %p, %p)", - (void*)lfs2, (void*)dir, (void*)info); +static int lfs2_dir_rawread(lfs2_t *lfs2, lfs2_dir_t *dir, struct lfs2_info *info) { memset(info, 0, sizeof(*info)); // special offset for '.' and '..' @@ -2080,26 +2160,22 @@ int lfs2_dir_read(lfs2_t *lfs2, lfs2_dir_t *dir, struct lfs2_info *info) { info->type = LFS2_TYPE_DIR; strcpy(info->name, "."); dir->pos += 1; - LFS2_TRACE("lfs2_dir_read -> %d", true); return true; } else if (dir->pos == 1) { info->type = LFS2_TYPE_DIR; strcpy(info->name, ".."); dir->pos += 1; - LFS2_TRACE("lfs2_dir_read -> %d", true); return true; } while (true) { if (dir->id == dir->m.count) { if (!dir->m.split) { - LFS2_TRACE("lfs2_dir_read -> %d", false); return false; } int err = lfs2_dir_fetch(lfs2, &dir->m, dir->m.tail); if (err) { - LFS2_TRACE("lfs2_dir_read -> %d", err); return err; } @@ -2108,7 +2184,6 @@ int lfs2_dir_read(lfs2_t *lfs2, lfs2_dir_t *dir, struct lfs2_info *info) { int err = lfs2_dir_getinfo(lfs2, &dir->m, dir->id, info); if (err && err != LFS2_ERR_NOENT) { - LFS2_TRACE("lfs2_dir_read -> %d", err); return err; } @@ -2119,17 +2194,13 @@ int lfs2_dir_read(lfs2_t *lfs2, lfs2_dir_t *dir, struct lfs2_info *info) { } dir->pos += 1; - LFS2_TRACE("lfs2_dir_read -> %d", true); return true; } -int lfs2_dir_seek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off) { - LFS2_TRACE("lfs2_dir_seek(%p, %p, %"PRIu32")", - (void*)lfs2, (void*)dir, off); +static int lfs2_dir_rawseek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off) { // simply walk from head dir - int err = lfs2_dir_rewind(lfs2, dir); + int err = lfs2_dir_rawrewind(lfs2, dir); if (err) { - LFS2_TRACE("lfs2_dir_seek -> %d", err); return err; } @@ -2148,13 +2219,11 @@ int lfs2_dir_seek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off) { if (dir->id == dir->m.count) { if (!dir->m.split) { - LFS2_TRACE("lfs2_dir_seek -> %d", LFS2_ERR_INVAL); return LFS2_ERR_INVAL; } err = lfs2_dir_fetch(lfs2, &dir->m, dir->m.tail); if (err) { - LFS2_TRACE("lfs2_dir_seek -> %d", err); return err; } @@ -2162,29 +2231,23 @@ int lfs2_dir_seek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off) { } } - LFS2_TRACE("lfs2_dir_seek -> %d", 0); return 0; } -lfs2_soff_t lfs2_dir_tell(lfs2_t *lfs2, lfs2_dir_t *dir) { - LFS2_TRACE("lfs2_dir_tell(%p, %p)", (void*)lfs2, (void*)dir); +static lfs2_soff_t lfs2_dir_rawtell(lfs2_t *lfs2, lfs2_dir_t *dir) { (void)lfs2; - LFS2_TRACE("lfs2_dir_tell -> %"PRId32, dir->pos); return dir->pos; } -int lfs2_dir_rewind(lfs2_t *lfs2, lfs2_dir_t *dir) { - LFS2_TRACE("lfs2_dir_rewind(%p, %p)", (void*)lfs2, (void*)dir); +static int lfs2_dir_rawrewind(lfs2_t *lfs2, lfs2_dir_t *dir) { // reload the head dir int err = lfs2_dir_fetch(lfs2, &dir->m, dir->head); if (err) { - LFS2_TRACE("lfs2_dir_rewind -> %d", err); return err; } dir->id = 0; dir->pos = 0; - LFS2_TRACE("lfs2_dir_rewind -> %d", 0); return 0; } @@ -2237,6 +2300,7 @@ static int lfs2_ctz_find(lfs2_t *lfs2, return 0; } +#ifndef LFS2_READONLY static int lfs2_ctz_extend(lfs2_t *lfs2, lfs2_cache_t *pcache, lfs2_cache_t *rcache, lfs2_block_t head, lfs2_size_t size, @@ -2334,6 +2398,7 @@ static int lfs2_ctz_extend(lfs2_t *lfs2, lfs2_cache_drop(lfs2, pcache); } } +#endif static int lfs2_ctz_traverse(lfs2_t *lfs2, const lfs2_cache_t *pcache, lfs2_cache_t *rcache, @@ -2380,27 +2445,25 @@ static int lfs2_ctz_traverse(lfs2_t *lfs2, /// Top level file operations /// -int lfs2_file_opencfg(lfs2_t *lfs2, lfs2_file_t *file, +static int lfs2_file_rawopencfg(lfs2_t *lfs2, lfs2_file_t *file, const char *path, int flags, const struct lfs2_file_config *cfg) { - LFS2_TRACE("lfs2_file_opencfg(%p, %p, \"%s\", %x, %p {" - ".buffer=%p, .attrs=%p, .attr_count=%"PRIu32"})", - (void*)lfs2, (void*)file, path, flags, - (void*)cfg, cfg->buffer, (void*)cfg->attrs, cfg->attr_count); - +#ifndef LFS2_READONLY // deorphan if we haven't yet, needed at most once after poweron - if ((flags & 3) != LFS2_O_RDONLY) { + if ((flags & LFS2_O_WRONLY) == LFS2_O_WRONLY) { int err = lfs2_fs_forceconsistency(lfs2); if (err) { - LFS2_TRACE("lfs2_file_opencfg -> %d", err); return err; } } +#else + LFS2_ASSERT((flags & LFS2_O_RDONLY) == LFS2_O_RDONLY); +#endif // setup simple file details int err; file->cfg = cfg; - file->flags = flags | LFS2_F_OPENED; + file->flags = flags; file->pos = 0; file->off = 0; file->cache.buffer = NULL; @@ -2414,9 +2477,13 @@ int lfs2_file_opencfg(lfs2_t *lfs2, lfs2_file_t *file, // get id, add to list of mdirs to catch update changes file->type = LFS2_TYPE_REG; - file->next = (lfs2_file_t*)lfs2->mlist; - lfs2->mlist = (struct lfs2_mlist*)file; + lfs2_mlist_append(lfs2, (struct lfs2_mlist *)file); +#ifdef LFS2_READONLY + if (tag == LFS2_ERR_NOENT) { + err = LFS2_ERR_NOENT; + goto cleanup; +#else if (tag == LFS2_ERR_NOENT) { if (!(flags & LFS2_O_CREAT)) { err = LFS2_ERR_NOENT; @@ -2432,9 +2499,9 @@ int lfs2_file_opencfg(lfs2_t *lfs2, lfs2_file_t *file, // get next slot and create entry to remember name err = lfs2_dir_commit(lfs2, &file->m, LFS2_MKATTRS( - {LFS2_MKTAG(LFS2_TYPE_CREATE, file->id, 0)}, + {LFS2_MKTAG(LFS2_TYPE_CREATE, file->id, 0), NULL}, {LFS2_MKTAG(LFS2_TYPE_REG, file->id, nlen), path}, - {LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, file->id, 0)})); + {LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, file->id, 0), NULL})); if (err) { err = LFS2_ERR_NAMETOOLONG; goto cleanup; @@ -2444,13 +2511,16 @@ int lfs2_file_opencfg(lfs2_t *lfs2, lfs2_file_t *file, } else if (flags & LFS2_O_EXCL) { err = LFS2_ERR_EXIST; goto cleanup; +#endif } else if (lfs2_tag_type3(tag) != LFS2_TYPE_REG) { err = LFS2_ERR_ISDIR; goto cleanup; +#ifndef LFS2_READONLY } else if (flags & LFS2_O_TRUNC) { // truncate if requested tag = LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, file->id, 0); file->flags |= LFS2_F_DIRTY; +#endif } else { // try to load what's on disk, if it's inlined we'll fix it later tag = lfs2_dir_get(lfs2, &file->m, LFS2_MKTAG(0x700, 0x3ff, 0), @@ -2464,7 +2534,8 @@ int lfs2_file_opencfg(lfs2_t *lfs2, lfs2_file_t *file, // fetch attrs for (unsigned i = 0; i < file->cfg->attr_count; i++) { - if ((file->flags & 3) != LFS2_O_WRONLY) { + // if opened for read / read-write operations + if ((file->flags & LFS2_O_RDONLY) == LFS2_O_RDONLY) { lfs2_stag_t res = lfs2_dir_get(lfs2, &file->m, LFS2_MKTAG(0x7ff, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_USERATTR + file->cfg->attrs[i].type, @@ -2476,7 +2547,9 @@ int lfs2_file_opencfg(lfs2_t *lfs2, lfs2_file_t *file, } } - if ((file->flags & 3) != LFS2_O_RDONLY) { +#ifndef LFS2_READONLY + // if opened for write / read-write operations + if ((file->flags & LFS2_O_WRONLY) == LFS2_O_WRONLY) { if (file->cfg->attrs[i].size > lfs2->attr_max) { err = LFS2_ERR_NOSPC; goto cleanup; @@ -2484,6 +2557,7 @@ int lfs2_file_opencfg(lfs2_t *lfs2, lfs2_file_t *file, file->flags |= LFS2_F_DIRTY; } +#endif } // allocate buffer if needed @@ -2523,54 +2597,45 @@ int lfs2_file_opencfg(lfs2_t *lfs2, lfs2_file_t *file, } } - LFS2_TRACE("lfs2_file_opencfg -> %d", 0); return 0; cleanup: // clean up lingering resources +#ifndef LFS2_READONLY file->flags |= LFS2_F_ERRED; - lfs2_file_close(lfs2, file); - LFS2_TRACE("lfs2_file_opencfg -> %d", err); +#endif + lfs2_file_rawclose(lfs2, file); return err; } -int lfs2_file_open(lfs2_t *lfs2, lfs2_file_t *file, +static int lfs2_file_rawopen(lfs2_t *lfs2, lfs2_file_t *file, const char *path, int flags) { - LFS2_TRACE("lfs2_file_open(%p, %p, \"%s\", %x)", - (void*)lfs2, (void*)file, path, flags); static const struct lfs2_file_config defaults = {0}; - int err = lfs2_file_opencfg(lfs2, file, path, flags, &defaults); - LFS2_TRACE("lfs2_file_open -> %d", err); + int err = lfs2_file_rawopencfg(lfs2, file, path, flags, &defaults); return err; } -int lfs2_file_close(lfs2_t *lfs2, lfs2_file_t *file) { - LFS2_TRACE("lfs2_file_close(%p, %p)", (void*)lfs2, (void*)file); - LFS2_ASSERT(file->flags & LFS2_F_OPENED); - - int err = lfs2_file_sync(lfs2, file); +static int lfs2_file_rawclose(lfs2_t *lfs2, lfs2_file_t *file) { +#ifndef LFS2_READONLY + int err = lfs2_file_rawsync(lfs2, file); +#else + int err = 0; +#endif // remove from list of mdirs - for (struct lfs2_mlist **p = &lfs2->mlist; *p; p = &(*p)->next) { - if (*p == (struct lfs2_mlist*)file) { - *p = (*p)->next; - break; - } - } + lfs2_mlist_remove(lfs2, (struct lfs2_mlist*)file); // clean up memory if (!file->cfg->buffer) { lfs2_free(file->cache.buffer); } - file->flags &= ~LFS2_F_OPENED; - LFS2_TRACE("lfs2_file_close -> %d", err); return err; } -static int lfs2_file_relocate(lfs2_t *lfs2, lfs2_file_t *file) { - LFS2_ASSERT(file->flags & LFS2_F_OPENED); +#ifndef LFS2_READONLY +static int lfs2_file_relocate(lfs2_t *lfs2, lfs2_file_t *file) { while (true) { // just relocate what exists into new block lfs2_block_t nblock; @@ -2638,7 +2703,9 @@ static int lfs2_file_relocate(lfs2_t *lfs2, lfs2_file_t *file) { lfs2_cache_drop(lfs2, &lfs2->pcache); } } +#endif +#ifndef LFS2_READONLY static int lfs2_file_outline(lfs2_t *lfs2, lfs2_file_t *file) { file->off = file->pos; lfs2_alloc_ack(lfs2); @@ -2650,10 +2717,10 @@ static int lfs2_file_outline(lfs2_t *lfs2, lfs2_file_t *file) { file->flags &= ~LFS2_F_INLINE; return 0; } +#endif +#ifndef LFS2_READONLY static int lfs2_file_flush(lfs2_t *lfs2, lfs2_file_t *file) { - LFS2_ASSERT(file->flags & LFS2_F_OPENED); - if (file->flags & LFS2_F_READING) { if (!(file->flags & LFS2_F_INLINE)) { lfs2_cache_drop(lfs2, &file->cache); @@ -2669,7 +2736,7 @@ static int lfs2_file_flush(lfs2_t *lfs2, lfs2_file_t *file) { lfs2_file_t orig = { .ctz.head = file->ctz.head, .ctz.size = file->ctz.size, - .flags = LFS2_O_RDONLY | LFS2_F_OPENED, + .flags = LFS2_O_RDONLY, .pos = file->pos, .cache = lfs2->rcache, }; @@ -2679,12 +2746,12 @@ static int lfs2_file_flush(lfs2_t *lfs2, lfs2_file_t *file) { // copy over a byte at a time, leave it up to caching // to make this efficient uint8_t data; - lfs2_ssize_t res = lfs2_file_read(lfs2, &orig, &data, 1); + lfs2_ssize_t res = lfs2_file_rawread(lfs2, &orig, &data, 1); if (res < 0) { return res; } - res = lfs2_file_write(lfs2, file, &data, 1); + res = lfs2_file_rawwrite(lfs2, file, &data, 1); if (res < 0) { return res; } @@ -2730,24 +2797,22 @@ static int lfs2_file_flush(lfs2_t *lfs2, lfs2_file_t *file) { return 0; } +#endif -int lfs2_file_sync(lfs2_t *lfs2, lfs2_file_t *file) { - LFS2_TRACE("lfs2_file_sync(%p, %p)", (void*)lfs2, (void*)file); - LFS2_ASSERT(file->flags & LFS2_F_OPENED); - +#ifndef LFS2_READONLY +static int lfs2_file_rawsync(lfs2_t *lfs2, lfs2_file_t *file) { if (file->flags & LFS2_F_ERRED) { // it's not safe to do anything if our file errored - LFS2_TRACE("lfs2_file_sync -> %d", 0); return 0; } int err = lfs2_file_flush(lfs2, file); if (err) { file->flags |= LFS2_F_ERRED; - LFS2_TRACE("lfs2_file_sync -> %d", err); return err; } + if ((file->flags & LFS2_F_DIRTY) && !lfs2_pair_isnull(file->m.pair)) { // update dir entry @@ -2777,39 +2842,35 @@ int lfs2_file_sync(lfs2_t *lfs2, lfs2_file_t *file) { file->cfg->attr_count), file->cfg->attrs})); if (err) { file->flags |= LFS2_F_ERRED; - LFS2_TRACE("lfs2_file_sync -> %d", err); return err; } file->flags &= ~LFS2_F_DIRTY; } - LFS2_TRACE("lfs2_file_sync -> %d", 0); return 0; } +#endif -lfs2_ssize_t lfs2_file_read(lfs2_t *lfs2, lfs2_file_t *file, +static lfs2_ssize_t lfs2_file_rawread(lfs2_t *lfs2, lfs2_file_t *file, void *buffer, lfs2_size_t size) { - LFS2_TRACE("lfs2_file_read(%p, %p, %p, %"PRIu32")", - (void*)lfs2, (void*)file, buffer, size); - LFS2_ASSERT(file->flags & LFS2_F_OPENED); - LFS2_ASSERT((file->flags & 3) != LFS2_O_WRONLY); + LFS2_ASSERT((file->flags & LFS2_O_RDONLY) == LFS2_O_RDONLY); uint8_t *data = buffer; lfs2_size_t nsize = size; +#ifndef LFS2_READONLY if (file->flags & LFS2_F_WRITING) { // flush out any writes int err = lfs2_file_flush(lfs2, file); if (err) { - LFS2_TRACE("lfs2_file_read -> %d", err); return err; } } +#endif if (file->pos >= file->ctz.size) { // eof if past end - LFS2_TRACE("lfs2_file_read -> %d", 0); return 0; } @@ -2825,7 +2886,6 @@ lfs2_ssize_t lfs2_file_read(lfs2_t *lfs2, lfs2_file_t *file, file->ctz.head, file->ctz.size, file->pos, &file->block, &file->off); if (err) { - LFS2_TRACE("lfs2_file_read -> %d", err); return err; } } else { @@ -2845,7 +2905,6 @@ lfs2_ssize_t lfs2_file_read(lfs2_t *lfs2, lfs2_file_t *file, LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, file->id, 0), file->off, data, diff); if (err) { - LFS2_TRACE("lfs2_file_read -> %d", err); return err; } } else { @@ -2853,7 +2912,6 @@ lfs2_ssize_t lfs2_file_read(lfs2_t *lfs2, lfs2_file_t *file, NULL, &file->cache, lfs2->cfg->block_size, file->block, file->off, data, diff); if (err) { - LFS2_TRACE("lfs2_file_read -> %d", err); return err; } } @@ -2864,16 +2922,13 @@ lfs2_ssize_t lfs2_file_read(lfs2_t *lfs2, lfs2_file_t *file, nsize -= diff; } - LFS2_TRACE("lfs2_file_read -> %"PRId32, size); return size; } -lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, +#ifndef LFS2_READONLY +static lfs2_ssize_t lfs2_file_rawwrite(lfs2_t *lfs2, lfs2_file_t *file, const void *buffer, lfs2_size_t size) { - LFS2_TRACE("lfs2_file_write(%p, %p, %p, %"PRIu32")", - (void*)lfs2, (void*)file, buffer, size); - LFS2_ASSERT(file->flags & LFS2_F_OPENED); - LFS2_ASSERT((file->flags & 3) != LFS2_O_RDONLY); + LFS2_ASSERT((file->flags & LFS2_O_WRONLY) == LFS2_O_WRONLY); const uint8_t *data = buffer; lfs2_size_t nsize = size; @@ -2882,7 +2937,6 @@ lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, // drop any reads int err = lfs2_file_flush(lfs2, file); if (err) { - LFS2_TRACE("lfs2_file_write -> %d", err); return err; } } @@ -2893,7 +2947,6 @@ lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, if (file->pos + size > lfs2->file_max) { // Larger than file limit? - LFS2_TRACE("lfs2_file_write -> %d", LFS2_ERR_FBIG); return LFS2_ERR_FBIG; } @@ -2903,9 +2956,8 @@ lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, file->pos = file->ctz.size; while (file->pos < pos) { - lfs2_ssize_t res = lfs2_file_write(lfs2, file, &(uint8_t){0}, 1); + lfs2_ssize_t res = lfs2_file_rawwrite(lfs2, file, &(uint8_t){0}, 1); if (res < 0) { - LFS2_TRACE("lfs2_file_write -> %"PRId32, res); return res; } } @@ -2919,7 +2971,6 @@ lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, int err = lfs2_file_outline(lfs2, file); if (err) { file->flags |= LFS2_F_ERRED; - LFS2_TRACE("lfs2_file_write -> %d", err); return err; } } @@ -2936,7 +2987,6 @@ lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, file->pos-1, &file->block, &file->off); if (err) { file->flags |= LFS2_F_ERRED; - LFS2_TRACE("lfs2_file_write -> %d", err); return err; } @@ -2951,7 +3001,6 @@ lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, &file->block, &file->off); if (err) { file->flags |= LFS2_F_ERRED; - LFS2_TRACE("lfs2_file_write -> %d", err); return err; } } else { @@ -2972,7 +3021,6 @@ lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, goto relocate; } file->flags |= LFS2_F_ERRED; - LFS2_TRACE("lfs2_file_write -> %d", err); return err; } @@ -2981,7 +3029,6 @@ lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, err = lfs2_file_relocate(lfs2, file); if (err) { file->flags |= LFS2_F_ERRED; - LFS2_TRACE("lfs2_file_write -> %d", err); return err; } } @@ -2995,22 +3042,19 @@ lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, } file->flags &= ~LFS2_F_ERRED; - LFS2_TRACE("lfs2_file_write -> %"PRId32, size); return size; } +#endif -lfs2_soff_t lfs2_file_seek(lfs2_t *lfs2, lfs2_file_t *file, +static lfs2_soff_t lfs2_file_rawseek(lfs2_t *lfs2, lfs2_file_t *file, lfs2_soff_t off, int whence) { - LFS2_TRACE("lfs2_file_seek(%p, %p, %"PRId32", %d)", - (void*)lfs2, (void*)file, off, whence); - LFS2_ASSERT(file->flags & LFS2_F_OPENED); - +#ifndef LFS2_READONLY // write out everything beforehand, may be noop if rdonly int err = lfs2_file_flush(lfs2, file); if (err) { - LFS2_TRACE("lfs2_file_seek -> %d", err); return err; } +#endif // find new pos lfs2_off_t npos = file->pos; @@ -3024,34 +3068,28 @@ lfs2_soff_t lfs2_file_seek(lfs2_t *lfs2, lfs2_file_t *file, if (npos > lfs2->file_max) { // file position out of range - LFS2_TRACE("lfs2_file_seek -> %d", LFS2_ERR_INVAL); return LFS2_ERR_INVAL; } // update pos file->pos = npos; - LFS2_TRACE("lfs2_file_seek -> %"PRId32, npos); return npos; } -int lfs2_file_truncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t size) { - LFS2_TRACE("lfs2_file_truncate(%p, %p, %"PRIu32")", - (void*)lfs2, (void*)file, size); - LFS2_ASSERT(file->flags & LFS2_F_OPENED); - LFS2_ASSERT((file->flags & 3) != LFS2_O_RDONLY); +#ifndef LFS2_READONLY +static int lfs2_file_rawtruncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t size) { + LFS2_ASSERT((file->flags & LFS2_O_WRONLY) == LFS2_O_WRONLY); if (size > LFS2_FILE_MAX) { - LFS2_TRACE("lfs2_file_truncate -> %d", LFS2_ERR_INVAL); return LFS2_ERR_INVAL; } lfs2_off_t pos = file->pos; - lfs2_off_t oldsize = lfs2_file_size(lfs2, file); + lfs2_off_t oldsize = lfs2_file_rawsize(lfs2, file); if (size < oldsize) { // need to flush since directly changing metadata int err = lfs2_file_flush(lfs2, file); if (err) { - LFS2_TRACE("lfs2_file_truncate -> %d", err); return err; } @@ -3060,7 +3098,6 @@ int lfs2_file_truncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t size) { file->ctz.head, file->ctz.size, size, &file->block, &file->off); if (err) { - LFS2_TRACE("lfs2_file_truncate -> %d", err); return err; } @@ -3070,97 +3107,80 @@ int lfs2_file_truncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t size) { } else if (size > oldsize) { // flush+seek if not already at end if (file->pos != oldsize) { - lfs2_soff_t res = lfs2_file_seek(lfs2, file, 0, LFS2_SEEK_END); + lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, 0, LFS2_SEEK_END); if (res < 0) { - LFS2_TRACE("lfs2_file_truncate -> %"PRId32, res); return (int)res; } } // fill with zeros while (file->pos < size) { - lfs2_ssize_t res = lfs2_file_write(lfs2, file, &(uint8_t){0}, 1); + lfs2_ssize_t res = lfs2_file_rawwrite(lfs2, file, &(uint8_t){0}, 1); if (res < 0) { - LFS2_TRACE("lfs2_file_truncate -> %"PRId32, res); return (int)res; } } } // restore pos - lfs2_soff_t res = lfs2_file_seek(lfs2, file, pos, LFS2_SEEK_SET); + lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, pos, LFS2_SEEK_SET); if (res < 0) { - LFS2_TRACE("lfs2_file_truncate -> %"PRId32, res); return (int)res; } - LFS2_TRACE("lfs2_file_truncate -> %d", 0); return 0; } +#endif -lfs2_soff_t lfs2_file_tell(lfs2_t *lfs2, lfs2_file_t *file) { - LFS2_TRACE("lfs2_file_tell(%p, %p)", (void*)lfs2, (void*)file); - LFS2_ASSERT(file->flags & LFS2_F_OPENED); +static lfs2_soff_t lfs2_file_rawtell(lfs2_t *lfs2, lfs2_file_t *file) { (void)lfs2; - LFS2_TRACE("lfs2_file_tell -> %"PRId32, file->pos); return file->pos; } -int lfs2_file_rewind(lfs2_t *lfs2, lfs2_file_t *file) { - LFS2_TRACE("lfs2_file_rewind(%p, %p)", (void*)lfs2, (void*)file); - lfs2_soff_t res = lfs2_file_seek(lfs2, file, 0, LFS2_SEEK_SET); +static int lfs2_file_rawrewind(lfs2_t *lfs2, lfs2_file_t *file) { + lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, 0, LFS2_SEEK_SET); if (res < 0) { - LFS2_TRACE("lfs2_file_rewind -> %"PRId32, res); return (int)res; } - LFS2_TRACE("lfs2_file_rewind -> %d", 0); return 0; } -lfs2_soff_t lfs2_file_size(lfs2_t *lfs2, lfs2_file_t *file) { - LFS2_TRACE("lfs2_file_size(%p, %p)", (void*)lfs2, (void*)file); - LFS2_ASSERT(file->flags & LFS2_F_OPENED); +static lfs2_soff_t lfs2_file_rawsize(lfs2_t *lfs2, lfs2_file_t *file) { (void)lfs2; + +#ifndef LFS2_READONLY if (file->flags & LFS2_F_WRITING) { - LFS2_TRACE("lfs2_file_size -> %"PRId32, - lfs2_max(file->pos, file->ctz.size)); return lfs2_max(file->pos, file->ctz.size); - } else { - LFS2_TRACE("lfs2_file_size -> %"PRId32, file->ctz.size); - return file->ctz.size; } +#endif + + return file->ctz.size; } /// General fs operations /// -int lfs2_stat(lfs2_t *lfs2, const char *path, struct lfs2_info *info) { - LFS2_TRACE("lfs2_stat(%p, \"%s\", %p)", (void*)lfs2, path, (void*)info); +static int lfs2_rawstat(lfs2_t *lfs2, const char *path, struct lfs2_info *info) { lfs2_mdir_t cwd; lfs2_stag_t tag = lfs2_dir_find(lfs2, &cwd, &path, NULL); if (tag < 0) { - LFS2_TRACE("lfs2_stat -> %"PRId32, tag); return (int)tag; } - int err = lfs2_dir_getinfo(lfs2, &cwd, lfs2_tag_id(tag), info); - LFS2_TRACE("lfs2_stat -> %d", err); - return err; + return lfs2_dir_getinfo(lfs2, &cwd, lfs2_tag_id(tag), info); } -int lfs2_remove(lfs2_t *lfs2, const char *path) { - LFS2_TRACE("lfs2_remove(%p, \"%s\")", (void*)lfs2, path); +#ifndef LFS2_READONLY +static int lfs2_rawremove(lfs2_t *lfs2, const char *path) { // deorphan if we haven't yet, needed at most once after poweron int err = lfs2_fs_forceconsistency(lfs2); if (err) { - LFS2_TRACE("lfs2_remove -> %d", err); return err; } lfs2_mdir_t cwd; lfs2_stag_t tag = lfs2_dir_find(lfs2, &cwd, &path, NULL); if (tag < 0 || lfs2_tag_id(tag) == 0x3ff) { - LFS2_TRACE("lfs2_remove -> %"PRId32, (tag < 0) ? tag : LFS2_ERR_INVAL); return (tag < 0) ? (int)tag : LFS2_ERR_INVAL; } @@ -3172,19 +3192,16 @@ int lfs2_remove(lfs2_t *lfs2, const char *path) { lfs2_stag_t res = lfs2_dir_get(lfs2, &cwd, LFS2_MKTAG(0x700, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_STRUCT, lfs2_tag_id(tag), 8), pair); if (res < 0) { - LFS2_TRACE("lfs2_remove -> %"PRId32, res); return (int)res; } lfs2_pair_fromle32(pair); err = lfs2_dir_fetch(lfs2, &dir.m, pair); if (err) { - LFS2_TRACE("lfs2_remove -> %d", err); return err; } if (dir.m.count > 0 || dir.m.split) { - LFS2_TRACE("lfs2_remove -> %d", LFS2_ERR_NOTEMPTY); return LFS2_ERR_NOTEMPTY; } @@ -3200,10 +3217,9 @@ int lfs2_remove(lfs2_t *lfs2, const char *path) { // delete the entry err = lfs2_dir_commit(lfs2, &cwd, LFS2_MKATTRS( - {LFS2_MKTAG(LFS2_TYPE_DELETE, lfs2_tag_id(tag), 0)})); + {LFS2_MKTAG(LFS2_TYPE_DELETE, lfs2_tag_id(tag), 0), NULL})); if (err) { lfs2->mlist = dir.next; - LFS2_TRACE("lfs2_remove -> %d", err); return err; } @@ -3214,28 +3230,24 @@ int lfs2_remove(lfs2_t *lfs2, const char *path) { err = lfs2_fs_pred(lfs2, dir.m.pair, &cwd); if (err) { - LFS2_TRACE("lfs2_remove -> %d", err); return err; } err = lfs2_dir_drop(lfs2, &cwd, &dir.m); if (err) { - LFS2_TRACE("lfs2_remove -> %d", err); return err; } } - LFS2_TRACE("lfs2_remove -> %d", 0); return 0; } +#endif -int lfs2_rename(lfs2_t *lfs2, const char *oldpath, const char *newpath) { - LFS2_TRACE("lfs2_rename(%p, \"%s\", \"%s\")", (void*)lfs2, oldpath, newpath); - +#ifndef LFS2_READONLY +static int lfs2_rawrename(lfs2_t *lfs2, const char *oldpath, const char *newpath) { // deorphan if we haven't yet, needed at most once after poweron int err = lfs2_fs_forceconsistency(lfs2); if (err) { - LFS2_TRACE("lfs2_rename -> %d", err); return err; } @@ -3243,8 +3255,6 @@ int lfs2_rename(lfs2_t *lfs2, const char *oldpath, const char *newpath) { lfs2_mdir_t oldcwd; lfs2_stag_t oldtag = lfs2_dir_find(lfs2, &oldcwd, &oldpath, NULL); if (oldtag < 0 || lfs2_tag_id(oldtag) == 0x3ff) { - LFS2_TRACE("lfs2_rename -> %"PRId32, - (oldtag < 0) ? oldtag : LFS2_ERR_INVAL); return (oldtag < 0) ? (int)oldtag : LFS2_ERR_INVAL; } @@ -3254,8 +3264,6 @@ int lfs2_rename(lfs2_t *lfs2, const char *oldpath, const char *newpath) { lfs2_stag_t prevtag = lfs2_dir_find(lfs2, &newcwd, &newpath, &newid); if ((prevtag < 0 || lfs2_tag_id(prevtag) == 0x3ff) && !(prevtag == LFS2_ERR_NOENT && newid != 0x3ff)) { - LFS2_TRACE("lfs2_rename -> %"PRId32, - (prevtag < 0) ? prevtag : LFS2_ERR_INVAL); return (prevtag < 0) ? (int)prevtag : LFS2_ERR_INVAL; } @@ -3269,7 +3277,6 @@ int lfs2_rename(lfs2_t *lfs2, const char *oldpath, const char *newpath) { // check that name fits lfs2_size_t nlen = strlen(newpath); if (nlen > lfs2->name_max) { - LFS2_TRACE("lfs2_rename -> %d", LFS2_ERR_NAMETOOLONG); return LFS2_ERR_NAMETOOLONG; } @@ -3280,11 +3287,9 @@ int lfs2_rename(lfs2_t *lfs2, const char *oldpath, const char *newpath) { newoldid += 1; } } else if (lfs2_tag_type3(prevtag) != lfs2_tag_type3(oldtag)) { - LFS2_TRACE("lfs2_rename -> %d", LFS2_ERR_ISDIR); return LFS2_ERR_ISDIR; } else if (samepair && newid == newoldid) { // we're renaming to ourselves?? - LFS2_TRACE("lfs2_rename -> %d", 0); return 0; } else if (lfs2_tag_type3(prevtag) == LFS2_TYPE_DIR) { // must be empty before removal @@ -3292,7 +3297,6 @@ int lfs2_rename(lfs2_t *lfs2, const char *oldpath, const char *newpath) { lfs2_stag_t res = lfs2_dir_get(lfs2, &newcwd, LFS2_MKTAG(0x700, 0x3ff, 0), LFS2_MKTAG(LFS2_TYPE_STRUCT, newid, 8), prevpair); if (res < 0) { - LFS2_TRACE("lfs2_rename -> %"PRId32, res); return (int)res; } lfs2_pair_fromle32(prevpair); @@ -3300,12 +3304,10 @@ int lfs2_rename(lfs2_t *lfs2, const char *oldpath, const char *newpath) { // must be empty before removal err = lfs2_dir_fetch(lfs2, &prevdir.m, prevpair); if (err) { - LFS2_TRACE("lfs2_rename -> %d", err); return err; } if (prevdir.m.count > 0 || prevdir.m.split) { - LFS2_TRACE("lfs2_rename -> %d", LFS2_ERR_NOTEMPTY); return LFS2_ERR_NOTEMPTY; } @@ -3326,15 +3328,14 @@ int lfs2_rename(lfs2_t *lfs2, const char *oldpath, const char *newpath) { // move over all attributes err = lfs2_dir_commit(lfs2, &newcwd, LFS2_MKATTRS( {LFS2_MKTAG_IF(prevtag != LFS2_ERR_NOENT, - LFS2_TYPE_DELETE, newid, 0)}, - {LFS2_MKTAG(LFS2_TYPE_CREATE, newid, 0)}, + LFS2_TYPE_DELETE, newid, 0), NULL}, + {LFS2_MKTAG(LFS2_TYPE_CREATE, newid, 0), NULL}, {LFS2_MKTAG(lfs2_tag_type3(oldtag), newid, strlen(newpath)), newpath}, {LFS2_MKTAG(LFS2_FROM_MOVE, newid, lfs2_tag_id(oldtag)), &oldcwd}, {LFS2_MKTAG_IF(samepair, - LFS2_TYPE_DELETE, newoldid, 0)})); + LFS2_TYPE_DELETE, newoldid, 0), NULL})); if (err) { lfs2->mlist = prevdir.next; - LFS2_TRACE("lfs2_rename -> %d", err); return err; } @@ -3344,10 +3345,9 @@ int lfs2_rename(lfs2_t *lfs2, const char *oldpath, const char *newpath) { // prep gstate and delete move id lfs2_fs_prepmove(lfs2, 0x3ff, NULL); err = lfs2_dir_commit(lfs2, &oldcwd, LFS2_MKATTRS( - {LFS2_MKTAG(LFS2_TYPE_DELETE, lfs2_tag_id(oldtag), 0)})); + {LFS2_MKTAG(LFS2_TYPE_DELETE, lfs2_tag_id(oldtag), 0), NULL})); if (err) { lfs2->mlist = prevdir.next; - LFS2_TRACE("lfs2_rename -> %d", err); return err; } } @@ -3359,29 +3359,24 @@ int lfs2_rename(lfs2_t *lfs2, const char *oldpath, const char *newpath) { err = lfs2_fs_pred(lfs2, prevdir.m.pair, &newcwd); if (err) { - LFS2_TRACE("lfs2_rename -> %d", err); return err; } err = lfs2_dir_drop(lfs2, &newcwd, &prevdir.m); if (err) { - LFS2_TRACE("lfs2_rename -> %d", err); return err; } } - LFS2_TRACE("lfs2_rename -> %d", 0); return 0; } +#endif -lfs2_ssize_t lfs2_getattr(lfs2_t *lfs2, const char *path, +static lfs2_ssize_t lfs2_rawgetattr(lfs2_t *lfs2, const char *path, uint8_t type, void *buffer, lfs2_size_t size) { - LFS2_TRACE("lfs2_getattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", - (void*)lfs2, path, type, buffer, size); lfs2_mdir_t cwd; lfs2_stag_t tag = lfs2_dir_find(lfs2, &cwd, &path, NULL); if (tag < 0) { - LFS2_TRACE("lfs2_getattr -> %"PRId32, tag); return tag; } @@ -3391,7 +3386,6 @@ lfs2_ssize_t lfs2_getattr(lfs2_t *lfs2, const char *path, id = 0; int err = lfs2_dir_fetch(lfs2, &cwd, lfs2->root); if (err) { - LFS2_TRACE("lfs2_getattr -> %d", err); return err; } } @@ -3402,19 +3396,16 @@ lfs2_ssize_t lfs2_getattr(lfs2_t *lfs2, const char *path, buffer); if (tag < 0) { if (tag == LFS2_ERR_NOENT) { - LFS2_TRACE("lfs2_getattr -> %d", LFS2_ERR_NOATTR); return LFS2_ERR_NOATTR; } - LFS2_TRACE("lfs2_getattr -> %"PRId32, tag); return tag; } - size = lfs2_tag_size(tag); - LFS2_TRACE("lfs2_getattr -> %"PRId32, size); - return size; + return lfs2_tag_size(tag); } +#ifndef LFS2_READONLY static int lfs2_commitattr(lfs2_t *lfs2, const char *path, uint8_t type, const void *buffer, lfs2_size_t size) { lfs2_mdir_t cwd; @@ -3436,27 +3427,24 @@ static int lfs2_commitattr(lfs2_t *lfs2, const char *path, return lfs2_dir_commit(lfs2, &cwd, LFS2_MKATTRS( {LFS2_MKTAG(LFS2_TYPE_USERATTR + type, id, size), buffer})); } +#endif -int lfs2_setattr(lfs2_t *lfs2, const char *path, +#ifndef LFS2_READONLY +static int lfs2_rawsetattr(lfs2_t *lfs2, const char *path, uint8_t type, const void *buffer, lfs2_size_t size) { - LFS2_TRACE("lfs2_setattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", - (void*)lfs2, path, type, buffer, size); if (size > lfs2->attr_max) { - LFS2_TRACE("lfs2_setattr -> %d", LFS2_ERR_NOSPC); return LFS2_ERR_NOSPC; } - int err = lfs2_commitattr(lfs2, path, type, buffer, size); - LFS2_TRACE("lfs2_setattr -> %d", err); - return err; + return lfs2_commitattr(lfs2, path, type, buffer, size); } +#endif -int lfs2_removeattr(lfs2_t *lfs2, const char *path, uint8_t type) { - LFS2_TRACE("lfs2_removeattr(%p, \"%s\", %"PRIu8")", (void*)lfs2, path, type); - int err = lfs2_commitattr(lfs2, path, type, NULL, 0x3ff); - LFS2_TRACE("lfs2_removeattr -> %d", err); - return err; +#ifndef LFS2_READONLY +static int lfs2_rawremoveattr(lfs2_t *lfs2, const char *path, uint8_t type) { + return lfs2_commitattr(lfs2, path, type, NULL, 0x3ff); } +#endif /// Filesystem operations /// @@ -3584,28 +3572,12 @@ static int lfs2_deinit(lfs2_t *lfs2) { return 0; } -int lfs2_format(lfs2_t *lfs2, const struct lfs2_config *cfg) { - LFS2_TRACE("lfs2_format(%p, %p {.context=%p, " - ".read=%p, .prog=%p, .erase=%p, .sync=%p, " - ".read_size=%"PRIu32", .prog_size=%"PRIu32", " - ".block_size=%"PRIu32", .block_count=%"PRIu32", " - ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " - ".lookahead_size=%"PRIu32", .read_buffer=%p, " - ".prog_buffer=%p, .lookahead_buffer=%p, " - ".name_max=%"PRIu32", .file_max=%"PRIu32", " - ".attr_max=%"PRIu32"})", - (void*)lfs2, (void*)cfg, cfg->context, - (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, - (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, - cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, - cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, - cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, - cfg->name_max, cfg->file_max, cfg->attr_max); +#ifndef LFS2_READONLY +static int lfs2_rawformat(lfs2_t *lfs2, const struct lfs2_config *cfg) { int err = 0; { err = lfs2_init(lfs2, cfg); if (err) { - LFS2_TRACE("lfs2_format -> %d", err); return err; } @@ -3636,7 +3608,7 @@ int lfs2_format(lfs2_t *lfs2, const struct lfs2_config *cfg) { lfs2_superblock_tole32(&superblock); err = lfs2_dir_commit(lfs2, &root, LFS2_MKATTRS( - {LFS2_MKTAG(LFS2_TYPE_CREATE, 0, 0)}, + {LFS2_MKTAG(LFS2_TYPE_CREATE, 0, 0), NULL}, {LFS2_MKTAG(LFS2_TYPE_SUPERBLOCK, 0, 8), "littlefs"}, {LFS2_MKTAG(LFS2_TYPE_INLINESTRUCT, 0, sizeof(superblock)), &superblock})); @@ -3661,30 +3633,14 @@ int lfs2_format(lfs2_t *lfs2, const struct lfs2_config *cfg) { cleanup: lfs2_deinit(lfs2); - LFS2_TRACE("lfs2_format -> %d", err); return err; + } +#endif -int lfs2_mount(lfs2_t *lfs2, const struct lfs2_config *cfg) { - LFS2_TRACE("lfs2_mount(%p, %p {.context=%p, " - ".read=%p, .prog=%p, .erase=%p, .sync=%p, " - ".read_size=%"PRIu32", .prog_size=%"PRIu32", " - ".block_size=%"PRIu32", .block_count=%"PRIu32", " - ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " - ".lookahead_size=%"PRIu32", .read_buffer=%p, " - ".prog_buffer=%p, .lookahead_buffer=%p, " - ".name_max=%"PRIu32", .file_max=%"PRIu32", " - ".attr_max=%"PRIu32"})", - (void*)lfs2, (void*)cfg, cfg->context, - (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, - (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, - cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, - cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, - cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, - cfg->name_max, cfg->file_max, cfg->attr_max); +static int lfs2_rawmount(lfs2_t *lfs2, const struct lfs2_config *cfg) { int err = lfs2_init(lfs2, cfg); if (err) { - LFS2_TRACE("lfs2_mount -> %d", err); return err; } @@ -3797,28 +3753,25 @@ int lfs2_mount(lfs2_t *lfs2, const struct lfs2_config *cfg) { lfs2->gstate.tag += !lfs2_tag_isvalid(lfs2->gstate.tag); lfs2->gdisk = lfs2->gstate; - // setup free lookahead - lfs2_alloc_reset(lfs2); + // setup free lookahead, to distribute allocations uniformly across + // boots, we start the allocator at a random location + lfs2->free.off = lfs2->seed % lfs2->cfg->block_count; + lfs2_alloc_drop(lfs2); - LFS2_TRACE("lfs2_mount -> %d", 0); return 0; cleanup: - lfs2_unmount(lfs2); - LFS2_TRACE("lfs2_mount -> %d", err); + lfs2_rawunmount(lfs2); return err; } -int lfs2_unmount(lfs2_t *lfs2) { - LFS2_TRACE("lfs2_unmount(%p)", (void*)lfs2); - int err = lfs2_deinit(lfs2); - LFS2_TRACE("lfs2_unmount -> %d", err); - return err; +static int lfs2_rawunmount(lfs2_t *lfs2) { + return lfs2_deinit(lfs2); } /// Filesystem filesystem operations /// -int lfs2_fs_traverseraw(lfs2_t *lfs2, +int lfs2_fs_rawtraverse(lfs2_t *lfs2, int (*cb)(void *data, lfs2_block_t block), void *data, bool includeorphans) { // iterate over metadata pairs @@ -3888,6 +3841,7 @@ int lfs2_fs_traverseraw(lfs2_t *lfs2, } } +#ifndef LFS2_READONLY // iterate over any open files for (lfs2_file_t *f = (lfs2_file_t*)lfs2->mlist; f; f = f->next) { if (f->type != LFS2_TYPE_REG) { @@ -3910,19 +3864,12 @@ int lfs2_fs_traverseraw(lfs2_t *lfs2, } } } +#endif return 0; } -int lfs2_fs_traverse(lfs2_t *lfs2, - int (*cb)(void *data, lfs2_block_t block), void *data) { - LFS2_TRACE("lfs2_fs_traverse(%p, %p, %p)", - (void*)lfs2, (void*)(uintptr_t)cb, data); - int err = lfs2_fs_traverseraw(lfs2, cb, data, true); - LFS2_TRACE("lfs2_fs_traverse -> %d", 0); - return err; -} - +#ifndef LFS2_READONLY static int lfs2_fs_pred(lfs2_t *lfs2, const lfs2_block_t pair[2], lfs2_mdir_t *pdir) { // iterate over all directory directory entries @@ -3948,12 +3895,16 @@ static int lfs2_fs_pred(lfs2_t *lfs2, return LFS2_ERR_NOENT; } +#endif +#ifndef LFS2_READONLY struct lfs2_fs_parent_match { lfs2_t *lfs2; const lfs2_block_t pair[2]; }; +#endif +#ifndef LFS2_READONLY static int lfs2_fs_parent_match(void *data, lfs2_tag_t tag, const void *buffer) { struct lfs2_fs_parent_match *find = data; @@ -3972,7 +3923,9 @@ static int lfs2_fs_parent_match(void *data, lfs2_pair_fromle32(child); return (lfs2_pair_cmp(child, find->pair) == 0) ? LFS2_CMP_EQ : LFS2_CMP_LT; } +#endif +#ifndef LFS2_READONLY static lfs2_stag_t lfs2_fs_parent(lfs2_t *lfs2, const lfs2_block_t pair[2], lfs2_mdir_t *parent) { // use fetchmatch with callback to find pairs @@ -3999,7 +3952,9 @@ static lfs2_stag_t lfs2_fs_parent(lfs2_t *lfs2, const lfs2_block_t pair[2], return LFS2_ERR_NOENT; } +#endif +#ifndef LFS2_READONLY static int lfs2_fs_relocate(lfs2_t *lfs2, const lfs2_block_t oldpair[2], lfs2_block_t newpair[2]) { // update internal root @@ -4050,7 +4005,7 @@ static int lfs2_fs_relocate(lfs2_t *lfs2, lfs2_pair_tole32(newpair); int err = lfs2_dir_commit(lfs2, &parent, LFS2_MKATTRS( {LFS2_MKTAG_IF(moveid != 0x3ff, - LFS2_TYPE_DELETE, moveid, 0)}, + LFS2_TYPE_DELETE, moveid, 0), NULL}, {tag, newpair})); lfs2_pair_fromle32(newpair); if (err) { @@ -4084,7 +4039,7 @@ static int lfs2_fs_relocate(lfs2_t *lfs2, lfs2_pair_tole32(newpair); err = lfs2_dir_commit(lfs2, &parent, LFS2_MKATTRS( {LFS2_MKTAG_IF(moveid != 0x3ff, - LFS2_TYPE_DELETE, moveid, 0)}, + LFS2_TYPE_DELETE, moveid, 0), NULL}, {LFS2_MKTAG(LFS2_TYPE_TAIL + parent.split, 0x3ff, 8), newpair})); lfs2_pair_fromle32(newpair); if (err) { @@ -4094,14 +4049,18 @@ static int lfs2_fs_relocate(lfs2_t *lfs2, return 0; } +#endif +#ifndef LFS2_READONLY static void lfs2_fs_preporphans(lfs2_t *lfs2, int8_t orphans) { LFS2_ASSERT(lfs2_tag_size(lfs2->gstate.tag) > 0 || orphans >= 0); lfs2->gstate.tag += orphans; lfs2->gstate.tag = ((lfs2->gstate.tag & ~LFS2_MKTAG(0x800, 0, 0)) | ((uint32_t)lfs2_gstate_hasorphans(&lfs2->gstate) << 31)); } +#endif +#ifndef LFS2_READONLY static void lfs2_fs_prepmove(lfs2_t *lfs2, uint16_t id, const lfs2_block_t pair[2]) { lfs2->gstate.tag = ((lfs2->gstate.tag & ~LFS2_MKTAG(0x7ff, 0x3ff, 0)) | @@ -4109,7 +4068,9 @@ static void lfs2_fs_prepmove(lfs2_t *lfs2, lfs2->gstate.pair[0] = (id != 0x3ff) ? pair[0] : 0; lfs2->gstate.pair[1] = (id != 0x3ff) ? pair[1] : 0; } +#endif +#ifndef LFS2_READONLY static int lfs2_fs_demove(lfs2_t *lfs2) { if (!lfs2_gstate_hasmove(&lfs2->gdisk)) { return 0; @@ -4132,14 +4093,16 @@ static int lfs2_fs_demove(lfs2_t *lfs2) { uint16_t moveid = lfs2_tag_id(lfs2->gdisk.tag); lfs2_fs_prepmove(lfs2, 0x3ff, NULL); err = lfs2_dir_commit(lfs2, &movedir, LFS2_MKATTRS( - {LFS2_MKTAG(LFS2_TYPE_DELETE, moveid, 0)})); + {LFS2_MKTAG(LFS2_TYPE_DELETE, moveid, 0), NULL})); if (err) { return err; } return 0; } +#endif +#ifndef LFS2_READONLY static int lfs2_fs_deorphan(lfs2_t *lfs2) { if (!lfs2_gstate_hasorphans(&lfs2->gstate)) { return 0; @@ -4213,7 +4176,9 @@ static int lfs2_fs_deorphan(lfs2_t *lfs2) { lfs2_fs_preporphans(lfs2, -lfs2_gstate_getorphans(&lfs2->gstate)); return 0; } +#endif +#ifndef LFS2_READONLY static int lfs2_fs_forceconsistency(lfs2_t *lfs2) { int err = lfs2_fs_demove(lfs2); if (err) { @@ -4227,6 +4192,7 @@ static int lfs2_fs_forceconsistency(lfs2_t *lfs2) { return 0; } +#endif static int lfs2_fs_size_count(void *p, lfs2_block_t block) { (void)block; @@ -4235,16 +4201,13 @@ static int lfs2_fs_size_count(void *p, lfs2_block_t block) { return 0; } -lfs2_ssize_t lfs2_fs_size(lfs2_t *lfs2) { - LFS2_TRACE("lfs2_fs_size(%p)", (void*)lfs2); +static lfs2_ssize_t lfs2_fs_rawsize(lfs2_t *lfs2) { lfs2_size_t size = 0; - int err = lfs2_fs_traverseraw(lfs2, lfs2_fs_size_count, &size, false); + int err = lfs2_fs_rawtraverse(lfs2, lfs2_fs_size_count, &size, false); if (err) { - LFS2_TRACE("lfs2_fs_size -> %d", err); return err; } - LFS2_TRACE("lfs2_fs_size -> %d", err); return size; } @@ -4669,27 +4632,10 @@ static int lfs21_unmount(lfs2_t *lfs2) { } /// v1 migration /// -int lfs2_migrate(lfs2_t *lfs2, const struct lfs2_config *cfg) { - LFS2_TRACE("lfs2_migrate(%p, %p {.context=%p, " - ".read=%p, .prog=%p, .erase=%p, .sync=%p, " - ".read_size=%"PRIu32", .prog_size=%"PRIu32", " - ".block_size=%"PRIu32", .block_count=%"PRIu32", " - ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " - ".lookahead_size=%"PRIu32", .read_buffer=%p, " - ".prog_buffer=%p, .lookahead_buffer=%p, " - ".name_max=%"PRIu32", .file_max=%"PRIu32", " - ".attr_max=%"PRIu32"})", - (void*)lfs2, (void*)cfg, cfg->context, - (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, - (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, - cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, - cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, - cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, - cfg->name_max, cfg->file_max, cfg->attr_max); +static int lfs2_rawmigrate(lfs2_t *lfs2, const struct lfs2_config *cfg) { struct lfs21 lfs21; int err = lfs21_mount(lfs2, &lfs21, cfg); if (err) { - LFS2_TRACE("lfs2_migrate -> %d", err); return err; } @@ -4906,8 +4852,539 @@ int lfs2_migrate(lfs2_t *lfs2, const struct lfs2_config *cfg) { cleanup: lfs21_unmount(lfs2); - LFS2_TRACE("lfs2_migrate -> %d", err); return err; } #endif + + +/// Public API wrappers /// + +// Here we can add tracing/thread safety easily + +// Thread-safe wrappers if enabled +#ifdef LFS2_THREADSAFE +#define LFS2_LOCK(cfg) cfg->lock(cfg) +#define LFS2_UNLOCK(cfg) cfg->unlock(cfg) +#else +#define LFS2_LOCK(cfg) ((void)cfg, 0) +#define LFS2_UNLOCK(cfg) ((void)cfg) +#endif + +// Public API +#ifndef LFS2_READONLY +int lfs2_format(lfs2_t *lfs2, const struct lfs2_config *cfg) { + int err = LFS2_LOCK(cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_format(%p, %p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32", " + ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " + ".lookahead_size=%"PRIu32", .read_buffer=%p, " + ".prog_buffer=%p, .lookahead_buffer=%p, " + ".name_max=%"PRIu32", .file_max=%"PRIu32", " + ".attr_max=%"PRIu32"})", + (void*)lfs2, (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, + cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, + cfg->name_max, cfg->file_max, cfg->attr_max); + + err = lfs2_rawformat(lfs2, cfg); + + LFS2_TRACE("lfs2_format -> %d", err); + LFS2_UNLOCK(cfg); + return err; +} +#endif + +int lfs2_mount(lfs2_t *lfs2, const struct lfs2_config *cfg) { + int err = LFS2_LOCK(cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_mount(%p, %p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32", " + ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " + ".lookahead_size=%"PRIu32", .read_buffer=%p, " + ".prog_buffer=%p, .lookahead_buffer=%p, " + ".name_max=%"PRIu32", .file_max=%"PRIu32", " + ".attr_max=%"PRIu32"})", + (void*)lfs2, (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, + cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, + cfg->name_max, cfg->file_max, cfg->attr_max); + + err = lfs2_rawmount(lfs2, cfg); + + LFS2_TRACE("lfs2_mount -> %d", err); + LFS2_UNLOCK(cfg); + return err; +} + +int lfs2_unmount(lfs2_t *lfs2) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_unmount(%p)", (void*)lfs2); + + err = lfs2_rawunmount(lfs2); + + LFS2_TRACE("lfs2_unmount -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} + +#ifndef LFS2_READONLY +int lfs2_remove(lfs2_t *lfs2, const char *path) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_remove(%p, \"%s\")", (void*)lfs2, path); + + err = lfs2_rawremove(lfs2, path); + + LFS2_TRACE("lfs2_remove -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} +#endif + +#ifndef LFS2_READONLY +int lfs2_rename(lfs2_t *lfs2, const char *oldpath, const char *newpath) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_rename(%p, \"%s\", \"%s\")", (void*)lfs2, oldpath, newpath); + + err = lfs2_rawrename(lfs2, oldpath, newpath); + + LFS2_TRACE("lfs2_rename -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} +#endif + +int lfs2_stat(lfs2_t *lfs2, const char *path, struct lfs2_info *info) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_stat(%p, \"%s\", %p)", (void*)lfs2, path, (void*)info); + + err = lfs2_rawstat(lfs2, path, info); + + LFS2_TRACE("lfs2_stat -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} + +lfs2_ssize_t lfs2_getattr(lfs2_t *lfs2, const char *path, + uint8_t type, void *buffer, lfs2_size_t size) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_getattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", + (void*)lfs2, path, type, buffer, size); + + lfs2_ssize_t res = lfs2_rawgetattr(lfs2, path, type, buffer, size); + + LFS2_TRACE("lfs2_getattr -> %"PRId32, res); + LFS2_UNLOCK(lfs2->cfg); + return res; +} + +#ifndef LFS2_READONLY +int lfs2_setattr(lfs2_t *lfs2, const char *path, + uint8_t type, const void *buffer, lfs2_size_t size) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_setattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", + (void*)lfs2, path, type, buffer, size); + + err = lfs2_rawsetattr(lfs2, path, type, buffer, size); + + LFS2_TRACE("lfs2_setattr -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} +#endif + +#ifndef LFS2_READONLY +int lfs2_removeattr(lfs2_t *lfs2, const char *path, uint8_t type) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_removeattr(%p, \"%s\", %"PRIu8")", (void*)lfs2, path, type); + + err = lfs2_rawremoveattr(lfs2, path, type); + + LFS2_TRACE("lfs2_removeattr -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} +#endif + +int lfs2_file_open(lfs2_t *lfs2, lfs2_file_t *file, const char *path, int flags) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_file_open(%p, %p, \"%s\", %x)", + (void*)lfs2, (void*)file, path, flags); + LFS2_ASSERT(!lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); + + err = lfs2_file_rawopen(lfs2, file, path, flags); + + LFS2_TRACE("lfs2_file_open -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} + +int lfs2_file_opencfg(lfs2_t *lfs2, lfs2_file_t *file, + const char *path, int flags, + const struct lfs2_file_config *cfg) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_file_opencfg(%p, %p, \"%s\", %x, %p {" + ".buffer=%p, .attrs=%p, .attr_count=%"PRIu32"})", + (void*)lfs2, (void*)file, path, flags, + (void*)cfg, cfg->buffer, (void*)cfg->attrs, cfg->attr_count); + LFS2_ASSERT(!lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); + + err = lfs2_file_rawopencfg(lfs2, file, path, flags, cfg); + + LFS2_TRACE("lfs2_file_opencfg -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} + +int lfs2_file_close(lfs2_t *lfs2, lfs2_file_t *file) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_file_close(%p, %p)", (void*)lfs2, (void*)file); + LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); + + err = lfs2_file_rawclose(lfs2, file); + + LFS2_TRACE("lfs2_file_close -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} + +#ifndef LFS2_READONLY +int lfs2_file_sync(lfs2_t *lfs2, lfs2_file_t *file) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_file_sync(%p, %p)", (void*)lfs2, (void*)file); + LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); + + err = lfs2_file_rawsync(lfs2, file); + + LFS2_TRACE("lfs2_file_sync -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} +#endif + +lfs2_ssize_t lfs2_file_read(lfs2_t *lfs2, lfs2_file_t *file, + void *buffer, lfs2_size_t size) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_file_read(%p, %p, %p, %"PRIu32")", + (void*)lfs2, (void*)file, buffer, size); + LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); + + lfs2_ssize_t res = lfs2_file_rawread(lfs2, file, buffer, size); + + LFS2_TRACE("lfs2_file_read -> %"PRId32, res); + LFS2_UNLOCK(lfs2->cfg); + return res; +} + +#ifndef LFS2_READONLY +lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, + const void *buffer, lfs2_size_t size) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_file_write(%p, %p, %p, %"PRIu32")", + (void*)lfs2, (void*)file, buffer, size); + LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); + + lfs2_ssize_t res = lfs2_file_rawwrite(lfs2, file, buffer, size); + + LFS2_TRACE("lfs2_file_write -> %"PRId32, res); + LFS2_UNLOCK(lfs2->cfg); + return res; +} +#endif + +lfs2_soff_t lfs2_file_seek(lfs2_t *lfs2, lfs2_file_t *file, + lfs2_soff_t off, int whence) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_file_seek(%p, %p, %"PRId32", %d)", + (void*)lfs2, (void*)file, off, whence); + LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); + + lfs2_soff_t res = lfs2_file_rawseek(lfs2, file, off, whence); + + LFS2_TRACE("lfs2_file_seek -> %"PRId32, res); + LFS2_UNLOCK(lfs2->cfg); + return res; +} + +#ifndef LFS2_READONLY +int lfs2_file_truncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t size) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_file_truncate(%p, %p, %"PRIu32")", + (void*)lfs2, (void*)file, size); + LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); + + err = lfs2_file_rawtruncate(lfs2, file, size); + + LFS2_TRACE("lfs2_file_truncate -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} +#endif + +lfs2_soff_t lfs2_file_tell(lfs2_t *lfs2, lfs2_file_t *file) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_file_tell(%p, %p)", (void*)lfs2, (void*)file); + LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); + + lfs2_soff_t res = lfs2_file_rawtell(lfs2, file); + + LFS2_TRACE("lfs2_file_tell -> %"PRId32, res); + LFS2_UNLOCK(lfs2->cfg); + return res; +} + +int lfs2_file_rewind(lfs2_t *lfs2, lfs2_file_t *file) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_file_rewind(%p, %p)", (void*)lfs2, (void*)file); + + err = lfs2_file_rawrewind(lfs2, file); + + LFS2_TRACE("lfs2_file_rewind -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} + +lfs2_soff_t lfs2_file_size(lfs2_t *lfs2, lfs2_file_t *file) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_file_size(%p, %p)", (void*)lfs2, (void*)file); + LFS2_ASSERT(lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)file)); + + lfs2_soff_t res = lfs2_file_rawsize(lfs2, file); + + LFS2_TRACE("lfs2_file_size -> %"PRId32, res); + LFS2_UNLOCK(lfs2->cfg); + return res; +} + +#ifndef LFS2_READONLY +int lfs2_mkdir(lfs2_t *lfs2, const char *path) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_mkdir(%p, \"%s\")", (void*)lfs2, path); + + err = lfs2_rawmkdir(lfs2, path); + + LFS2_TRACE("lfs2_mkdir -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} +#endif + +int lfs2_dir_open(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_dir_open(%p, %p, \"%s\")", (void*)lfs2, (void*)dir, path); + LFS2_ASSERT(!lfs2_mlist_isopen(lfs2->mlist, (struct lfs2_mlist*)dir)); + + err = lfs2_dir_rawopen(lfs2, dir, path); + + LFS2_TRACE("lfs2_dir_open -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} + +int lfs2_dir_close(lfs2_t *lfs2, lfs2_dir_t *dir) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_dir_close(%p, %p)", (void*)lfs2, (void*)dir); + + err = lfs2_dir_rawclose(lfs2, dir); + + LFS2_TRACE("lfs2_dir_close -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} + +int lfs2_dir_read(lfs2_t *lfs2, lfs2_dir_t *dir, struct lfs2_info *info) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_dir_read(%p, %p, %p)", + (void*)lfs2, (void*)dir, (void*)info); + + err = lfs2_dir_rawread(lfs2, dir, info); + + LFS2_TRACE("lfs2_dir_read -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} + +int lfs2_dir_seek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_dir_seek(%p, %p, %"PRIu32")", + (void*)lfs2, (void*)dir, off); + + err = lfs2_dir_rawseek(lfs2, dir, off); + + LFS2_TRACE("lfs2_dir_seek -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} + +lfs2_soff_t lfs2_dir_tell(lfs2_t *lfs2, lfs2_dir_t *dir) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_dir_tell(%p, %p)", (void*)lfs2, (void*)dir); + + lfs2_soff_t res = lfs2_dir_rawtell(lfs2, dir); + + LFS2_TRACE("lfs2_dir_tell -> %"PRId32, res); + LFS2_UNLOCK(lfs2->cfg); + return res; +} + +int lfs2_dir_rewind(lfs2_t *lfs2, lfs2_dir_t *dir) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_dir_rewind(%p, %p)", (void*)lfs2, (void*)dir); + + err = lfs2_dir_rawrewind(lfs2, dir); + + LFS2_TRACE("lfs2_dir_rewind -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} + +lfs2_ssize_t lfs2_fs_size(lfs2_t *lfs2) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_fs_size(%p)", (void*)lfs2); + + lfs2_ssize_t res = lfs2_fs_rawsize(lfs2); + + LFS2_TRACE("lfs2_fs_size -> %"PRId32, res); + LFS2_UNLOCK(lfs2->cfg); + return res; +} + +int lfs2_fs_traverse(lfs2_t *lfs2, int (*cb)(void *, lfs2_block_t), void *data) { + int err = LFS2_LOCK(lfs2->cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_fs_traverse(%p, %p, %p)", + (void*)lfs2, (void*)(uintptr_t)cb, data); + + err = lfs2_fs_rawtraverse(lfs2, cb, data, true); + + LFS2_TRACE("lfs2_fs_traverse -> %d", err); + LFS2_UNLOCK(lfs2->cfg); + return err; +} + +#ifdef LFS2_MIGRATE +int lfs2_migrate(lfs2_t *lfs2, const struct lfs2_config *cfg) { + int err = LFS2_LOCK(cfg); + if (err) { + return err; + } + LFS2_TRACE("lfs2_migrate(%p, %p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32", " + ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " + ".lookahead_size=%"PRIu32", .read_buffer=%p, " + ".prog_buffer=%p, .lookahead_buffer=%p, " + ".name_max=%"PRIu32", .file_max=%"PRIu32", " + ".attr_max=%"PRIu32"})", + (void*)lfs2, (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, + cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, + cfg->name_max, cfg->file_max, cfg->attr_max); + + err = lfs2_rawmigrate(lfs2, cfg); + + LFS2_TRACE("lfs2_migrate -> %d", err); + LFS2_UNLOCK(cfg); + return err; +} +#endif + diff --git a/lib/littlefs/lfs2.h b/lib/littlefs/lfs2.h index c89af79cabddd..e2afc7792ce4f 100644 --- a/lib/littlefs/lfs2.h +++ b/lib/littlefs/lfs2.h @@ -9,6 +9,7 @@ #include #include +#include "lfs2_util.h" #ifdef __cplusplus extern "C" @@ -21,7 +22,7 @@ extern "C" // Software library version // Major (top-nibble), incremented on backwards incompatible changes // Minor (bottom-nibble), incremented on feature additions -#define LFS2_VERSION 0x00020002 +#define LFS2_VERSION 0x00020003 #define LFS2_VERSION_MAJOR (0xffff & (LFS2_VERSION >> 16)) #define LFS2_VERSION_MINOR (0xffff & (LFS2_VERSION >> 0)) @@ -123,20 +124,25 @@ enum lfs2_type { enum lfs2_open_flags { // open flags LFS2_O_RDONLY = 1, // Open a file as read only +#ifndef LFS2_READONLY LFS2_O_WRONLY = 2, // Open a file as write only LFS2_O_RDWR = 3, // Open a file as read and write LFS2_O_CREAT = 0x0100, // Create a file if it does not exist LFS2_O_EXCL = 0x0200, // Fail if a file already exists LFS2_O_TRUNC = 0x0400, // Truncate the existing file to zero size LFS2_O_APPEND = 0x0800, // Move to end of file on every write +#endif // internally used flags +#ifndef LFS2_READONLY LFS2_F_DIRTY = 0x010000, // File does not match storage LFS2_F_WRITING = 0x020000, // File has been written since last flush +#endif LFS2_F_READING = 0x040000, // File has been read since last flush - LFS2_F_ERRED = 0x080000, // An error occured during write +#ifndef LFS2_READONLY + LFS2_F_ERRED = 0x080000, // An error occurred during write +#endif LFS2_F_INLINE = 0x100000, // Currently inlined in directory entry - LFS2_F_OPENED = 0x200000, // File has been opened }; // File seek flags @@ -174,6 +180,16 @@ struct lfs2_config { // are propogated to the user. int (*sync)(const struct lfs2_config *c); +#ifdef LFS2_THREADSAFE + // Lock the underlying block device. Negative error codes + // are propogated to the user. + int (*lock)(const struct lfs2_config *c); + + // Unlock the underlying block device. Negative error codes + // are propogated to the user. + int (*unlock)(const struct lfs2_config *c); +#endif + // Minimum size of a block read. All read operations will be a // multiple of this value. lfs2_size_t read_size; @@ -399,6 +415,7 @@ typedef struct lfs2 { /// Filesystem functions /// +#ifndef LFS2_READONLY // Format a block device with the littlefs // // Requires a littlefs object and config struct. This clobbers the littlefs @@ -407,6 +424,7 @@ typedef struct lfs2 { // // Returns a negative error code on failure. int lfs2_format(lfs2_t *lfs2, const struct lfs2_config *config); +#endif // Mounts a littlefs // @@ -426,12 +444,15 @@ int lfs2_unmount(lfs2_t *lfs2); /// General operations /// +#ifndef LFS2_READONLY // Removes a file or directory // // If removing a directory, the directory must be empty. // Returns a negative error code on failure. int lfs2_remove(lfs2_t *lfs2, const char *path); +#endif +#ifndef LFS2_READONLY // Rename or move a file or directory // // If the destination exists, it must match the source in type. @@ -439,6 +460,7 @@ int lfs2_remove(lfs2_t *lfs2, const char *path); // // Returns a negative error code on failure. int lfs2_rename(lfs2_t *lfs2, const char *oldpath, const char *newpath); +#endif // Find info about a file or directory // @@ -461,6 +483,7 @@ int lfs2_stat(lfs2_t *lfs2, const char *path, struct lfs2_info *info); lfs2_ssize_t lfs2_getattr(lfs2_t *lfs2, const char *path, uint8_t type, void *buffer, lfs2_size_t size); +#ifndef LFS2_READONLY // Set custom attributes // // Custom attributes are uniquely identified by an 8-bit type and limited @@ -470,13 +493,16 @@ lfs2_ssize_t lfs2_getattr(lfs2_t *lfs2, const char *path, // Returns a negative error code on failure. int lfs2_setattr(lfs2_t *lfs2, const char *path, uint8_t type, const void *buffer, lfs2_size_t size); +#endif +#ifndef LFS2_READONLY // Removes a custom attribute // // If an attribute is not found, nothing happens. // // Returns a negative error code on failure. int lfs2_removeattr(lfs2_t *lfs2, const char *path, uint8_t type); +#endif /// File operations /// @@ -525,6 +551,7 @@ int lfs2_file_sync(lfs2_t *lfs2, lfs2_file_t *file); lfs2_ssize_t lfs2_file_read(lfs2_t *lfs2, lfs2_file_t *file, void *buffer, lfs2_size_t size); +#ifndef LFS2_READONLY // Write data to file // // Takes a buffer and size indicating the data to write. The file will not @@ -533,6 +560,7 @@ lfs2_ssize_t lfs2_file_read(lfs2_t *lfs2, lfs2_file_t *file, // Returns the number of bytes written, or a negative error code on failure. lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, const void *buffer, lfs2_size_t size); +#endif // Change the position of the file // @@ -541,10 +569,12 @@ lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file, lfs2_soff_t lfs2_file_seek(lfs2_t *lfs2, lfs2_file_t *file, lfs2_soff_t off, int whence); +#ifndef LFS2_READONLY // Truncates the size of the file to the specified size // // Returns a negative error code on failure. int lfs2_file_truncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t size); +#endif // Return the position of the file // @@ -567,10 +597,12 @@ lfs2_soff_t lfs2_file_size(lfs2_t *lfs2, lfs2_file_t *file); /// Directory operations /// +#ifndef LFS2_READONLY // Create a directory // // Returns a negative error code on failure. int lfs2_mkdir(lfs2_t *lfs2, const char *path); +#endif // Open a directory // @@ -632,6 +664,7 @@ lfs2_ssize_t lfs2_fs_size(lfs2_t *lfs2); // Returns a negative error code on failure. int lfs2_fs_traverse(lfs2_t *lfs2, int (*cb)(void*, lfs2_block_t), void *data); +#ifndef LFS2_READONLY #ifdef LFS2_MIGRATE // Attempts to migrate a previous version of littlefs // @@ -646,6 +679,7 @@ int lfs2_fs_traverse(lfs2_t *lfs2, int (*cb)(void*, lfs2_block_t), void *data); // Returns a negative error code on failure. int lfs2_migrate(lfs2_t *lfs2, const struct lfs2_config *cfg); #endif +#endif #ifdef __cplusplus From 8a29319a157a69c1755d515e466b3e1c7ed8b76e Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 9 Dec 2020 13:18:22 +1100 Subject: [PATCH 149/179] lib/littlefs: Guard lfs2_mlist_isopen with LFS2_NO_ASSERT. To prevent warnings about this function being unused when assertions are disabled. Signed-off-by: Damien George --- lib/littlefs/lfs2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/littlefs/lfs2.c b/lib/littlefs/lfs2.c index 31f3c603e7b87..1f8c9d2d93d63 100644 --- a/lib/littlefs/lfs2.c +++ b/lib/littlefs/lfs2.c @@ -425,6 +425,7 @@ static inline void lfs2_superblock_tole32(lfs2_superblock_t *superblock) { superblock->attr_max = lfs2_tole32(superblock->attr_max); } +#ifndef LFS2_NO_ASSERT static inline bool lfs2_mlist_isopen(struct lfs2_mlist *head, struct lfs2_mlist *node) { for (struct lfs2_mlist **p = &head; *p; p = &(*p)->next) { @@ -435,6 +436,7 @@ static inline bool lfs2_mlist_isopen(struct lfs2_mlist *head, return false; } +#endif static inline void lfs2_mlist_remove(lfs2_t *lfs2, struct lfs2_mlist *mlist) { for (struct lfs2_mlist **p = &lfs2->mlist; *p; p = &(*p)->next) { From 460a181b77c875608c27dcb955560cff3a51f839 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 9 Dec 2020 11:02:47 +1100 Subject: [PATCH 150/179] stm32/mboot: Enable LFS2_READONLY for mboot builds with littlefs. To reduce code size, since mboot does not modify the filesystem. Signed-off-by: Damien George --- ports/stm32/mboot/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/mboot/Makefile b/ports/stm32/mboot/Makefile index 3d041e1c141ea..44372b0e8a4ae 100755 --- a/ports/stm32/mboot/Makefile +++ b/ports/stm32/mboot/Makefile @@ -71,7 +71,7 @@ CFLAGS += -DBOARD_$(BOARD) CFLAGS += -DAPPLICATION_ADDR=$(TEXT0_ADDR) CFLAGS += -DFFCONF_H=\"ports/stm32/mboot/ffconf.h\" CFLAGS += -DLFS1_NO_MALLOC -DLFS1_NO_DEBUG -DLFS1_NO_WARN -DLFS1_NO_ERROR -DLFS1_NO_ASSERT -CFLAGS += -DLFS2_NO_MALLOC -DLFS2_NO_DEBUG -DLFS2_NO_WARN -DLFS2_NO_ERROR -DLFS2_NO_ASSERT +CFLAGS += -DLFS2_NO_MALLOC -DLFS2_NO_DEBUG -DLFS2_NO_WARN -DLFS2_NO_ERROR -DLFS2_NO_ASSERT -DLFS2_READONLY CFLAGS += -DBUILDING_MBOOT=1 CFLAGS += -DMICROPY_HW_STM32WB_FLASH_SYNCRONISATION=0 CFLAGS += -DBOOTLOADER_DFU_USB_VID=$(BOOTLOADER_DFU_USB_VID) -DBOOTLOADER_DFU_USB_PID=$(BOOTLOADER_DFU_USB_PID) From f694a6fa20652e327151ac537b79180930c54ca0 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 7 Dec 2020 16:18:33 +1100 Subject: [PATCH 151/179] lib/stm32lib: Update library for WB v1.10.0. Changes in this new library version are: - Update WB HAL to v1.10.0. Signed-off-by: Damien George --- lib/stm32lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/stm32lib b/lib/stm32lib index 58fee7c92bd57..302c52794d2f5 160000 --- a/lib/stm32lib +++ b/lib/stm32lib @@ -1 +1 @@ -Subproject commit 58fee7c92bd576814d3f2afd92fbc62990270ecc +Subproject commit 302c52794d2f579903f4e49cbad1f5d3a7f401ad From f305c62a5fccb376db773fde6273e64e1fa9948f Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 7 Dec 2020 16:19:12 +1100 Subject: [PATCH 152/179] stm32/usb: Allocate 128 bytes to CDC data out EPs on non-multi-OTG MCUs. This much buffer space is required for CDC data out endpoints to avoid any buffer overflows when the USB CDC is saturated with data. Signed-off-by: Damien George --- ports/stm32/usb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/usb.c b/ports/stm32/usb.c index 968b2999ced37..5003bb27cc235 100644 --- a/ports/stm32/usb.c +++ b/ports/stm32/usb.c @@ -102,8 +102,8 @@ STATIC const uint8_t usbd_fifo_size_cdc1[USBD_PMA_NUM_FIFO] = { #if MICROPY_HW_USB_CDC_NUM >= 2 STATIC const uint8_t usbd_fifo_size_cdc2[USBD_PMA_NUM_FIFO] = { 8, 8, 16, 16, // EP0(out), EP0(in), MSC/HID(out), MSC/HID(in) - 0, 8, 12, 12, // unused, CDC_CMD(in), CDC_DATA(out), CDC_DATA(in) - 0, 8, 12, 12, // unused, CDC2_CMD(in), CDC2_DATA(out), CDC2_DATA(in) + 0, 8, 16, 8, // unused, CDC_CMD(in), CDC_DATA(out), CDC_DATA(in) + 0, 8, 16, 8, // unused, CDC2_CMD(in), CDC2_DATA(out), CDC2_DATA(in) 0, 0, 0, 0, // 4x unused }; From e0bb7a53c3244ffa720fcdb64be5784379c5f596 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 14 Dec 2020 10:29:57 +1100 Subject: [PATCH 153/179] tests/misc/sys_settrace_features.py: Ignore CPython zipimport traces. Signed-off-by: Damien George --- tests/misc/sys_settrace_features.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/misc/sys_settrace_features.py b/tests/misc/sys_settrace_features.py index a12304489231f..d315ea69730c0 100644 --- a/tests/misc/sys_settrace_features.py +++ b/tests/misc/sys_settrace_features.py @@ -60,7 +60,8 @@ def trace_tick_handler_bob(frame, event, arg): def trace_tick_handler(frame, event, arg): # Ignore CPython specific helpers. - if frame.f_globals["__name__"].find("importlib") != -1: + frame_name = frame.f_globals["__name__"] + if frame_name.find("importlib") != -1 or frame_name.find("zipimport") != -1: return print("### trace_handler::main event:", event) From 69262a11dcaae6db7ecbd1f4b6179a2ed6195a10 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 14 Dec 2020 13:05:43 +1100 Subject: [PATCH 154/179] tools/ci.sh: Put echo of CI path in a separate function. Because the setup functions may print other information which should not be added to the path. Signed-off-by: Damien George --- .github/workflows/ports_esp32.yml | 4 ++-- .github/workflows/ports_esp8266.yml | 2 +- tools/ci.sh | 9 +++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ports_esp32.yml b/.github/workflows/ports_esp32.yml index b89e462e049fc..041074557e616 100644 --- a/.github/workflows/ports_esp32.yml +++ b/.github/workflows/ports_esp32.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Install packages - run: source tools/ci.sh && ci_esp32_idf3_setup >> $GITHUB_PATH + run: source tools/ci.sh && ci_esp32_idf3_setup && ci_esp32_idf3_path >> $GITHUB_PATH - name: Build env: IDF_PATH: ${{ github.workspace }}/esp-idf @@ -29,7 +29,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Install packages - run: source tools/ci.sh && ci_esp32_idf4_setup >> $GITHUB_PATH + run: source tools/ci.sh && ci_esp32_idf4_setup && ci_esp32_idf4_path >> $GITHUB_PATH - name: Build env: IDF_PATH: ${{ github.workspace }}/esp-idf diff --git a/.github/workflows/ports_esp8266.yml b/.github/workflows/ports_esp8266.yml index f6f2fcaf4a80e..f4ce1f8212d87 100644 --- a/.github/workflows/ports_esp8266.yml +++ b/.github/workflows/ports_esp8266.yml @@ -18,6 +18,6 @@ jobs: steps: - uses: actions/checkout@v2 - name: Install packages - run: source tools/ci.sh && ci_esp8266_setup >> $GITHUB_PATH + run: source tools/ci.sh && ci_esp8266_setup && ci_esp8266_path >> $GITHUB_PATH - name: Build run: source tools/ci.sh && ci_esp8266_build diff --git a/tools/ci.sh b/tools/ci.sh index ded9fab922d50..32fccf071d5cb 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -75,6 +75,9 @@ function ci_esp32_idf3_setup { sudo pip3 install pyserial 'pyparsing<2.4' curl -L https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz | tar zxf - git clone https://github.com/espressif/esp-idf.git +} + +function ci_esp32_idf3_path { echo $(pwd)/xtensa-esp32-elf/bin } @@ -90,6 +93,9 @@ function ci_esp32_idf4_setup { sudo pip3 install pyserial 'pyparsing<2.4' curl -L https://dl.espressif.com/dl/xtensa-esp32-elf-gcc8_2_0-esp-2019r2-linux-amd64.tar.gz | tar zxf - git clone https://github.com/espressif/esp-idf.git +} + +function ci_esp32_idf4_path { echo $(pwd)/xtensa-esp32-elf/bin } @@ -108,6 +114,9 @@ function ci_esp8266_setup { sudo pip install pyserial wget https://github.com/jepler/esp-open-sdk/releases/download/2018-06-10/xtensa-lx106-elf-standalone.tar.gz zcat xtensa-lx106-elf-standalone.tar.gz | tar x +} + +function ci_esp8266_path { echo $(pwd)/xtensa-lx106-elf/bin } From ee52f89224ac7fde3794bcac7efa1ee4ee2bff81 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 14 Dec 2020 13:07:00 +1100 Subject: [PATCH 155/179] tools/ci.sh: Use pip-install to get latest version of esptool.py. Because the version included in xtensa-lx106-elf-standalone.tar.gz needs Python 2 (and pyserial for Python 2). Signed-off-by: Damien George --- tools/ci.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/ci.sh b/tools/ci.sh index 32fccf071d5cb..1a413ec991920 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -111,9 +111,11 @@ function ci_esp32_idf4_build { # ports/esp8266 function ci_esp8266_setup { - sudo pip install pyserial + sudo pip install pyserial esptool wget https://github.com/jepler/esp-open-sdk/releases/download/2018-06-10/xtensa-lx106-elf-standalone.tar.gz zcat xtensa-lx106-elf-standalone.tar.gz | tar x + # Remove this esptool.py so pip version is used instead + rm xtensa-lx106-elf/bin/esptool.py } function ci_esp8266_path { From 0091041f5a1bbf0a2d9ab7ad3de7169f8cc07cf1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 13 Dec 2020 16:00:39 +1100 Subject: [PATCH 156/179] py/modmath: Simplify handling of positional args to reduce code size. As a general pattern, required positional arguments that are not named do not need to be parsed using mp_arg_parse_all(). Signed-off-by: Damien George --- py/modmath.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/py/modmath.c b/py/modmath.c index 3ab3ff334c41b..ac9e0bbc449c6 100644 --- a/py/modmath.c +++ b/py/modmath.c @@ -204,17 +204,15 @@ MATH_FUN_1(lgamma, lgamma) #if MICROPY_PY_MATH_ISCLOSE STATIC mp_obj_t mp_math_isclose(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_a, ARG_b, ARG_rel_tol, ARG_abs_tol }; + enum { ARG_rel_tol, ARG_abs_tol }; static const mp_arg_t allowed_args[] = { - {MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, - {MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, {MP_QSTR_rel_tol, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, {MP_QSTR_abs_tol, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_SMALL_INT(0)}}, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - const mp_float_t a = mp_obj_get_float(args[ARG_a].u_obj); - const mp_float_t b = mp_obj_get_float(args[ARG_b].u_obj); + mp_arg_parse_all(n_args - 2, pos_args + 2, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + const mp_float_t a = mp_obj_get_float(pos_args[0]); + const mp_float_t b = mp_obj_get_float(pos_args[1]); const mp_float_t rel_tol = args[ARG_rel_tol].u_obj == MP_OBJ_NULL ? (mp_float_t)1e-9 : mp_obj_get_float(args[ARG_rel_tol].u_obj); const mp_float_t abs_tol = mp_obj_get_float(args[ARG_abs_tol].u_obj); From 246b2e016ab4a226cc0ac015f5cc340418f81ca1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 13 Dec 2020 16:15:04 +1100 Subject: [PATCH 157/179] py/mkrules.mk: Remove stray vpath and unused -Itmp, add $(Q) for $(AR). Signed-off-by: Damien George --- py/mkrules.mk | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/py/mkrules.mk b/py/mkrules.mk index 3bfe64d7536a1..eb8477cbb1d8b 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -15,7 +15,7 @@ CFLAGS += -DMICROPY_ROM_TEXT_COMPRESSION=1 endif # QSTR generation uses the same CFLAGS, with these modifications. -QSTR_GEN_FLAGS = -DNO_QSTR -I$(BUILD)/tmp +QSTR_GEN_FLAGS = -DNO_QSTR # Note: := to force evalulation immediately. QSTR_GEN_CFLAGS := $(CFLAGS) QSTR_GEN_CFLAGS += $(QSTR_GEN_FLAGS) @@ -76,8 +76,6 @@ vpath %.c . $(TOP) $(USER_C_MODULES) $(BUILD)/%.o: %.c $(call compile_c) -vpath %.c . $(TOP) $(USER_C_MODULES) - vpath %.cpp . $(TOP) $(USER_C_MODULES) $(BUILD)/%.o: %.cpp $(call compile_cxx) @@ -215,7 +213,7 @@ LIBMICROPYTHON = libmicropython.a # tracking. Then LIBMICROPYTHON_EXTRA_CMD can e.g. touch some # other file to cause needed effect, e.g. relinking with new lib. lib $(LIBMICROPYTHON): $(OBJ) - $(AR) rcs $(LIBMICROPYTHON) $^ + $(Q)$(AR) rcs $(LIBMICROPYTHON) $^ $(LIBMICROPYTHON_EXTRA_CMD) clean: From 1719459c28138be009c8dd41f0e6cb3b942eb2dd Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 13 Dec 2020 16:41:12 +1100 Subject: [PATCH 158/179] extmod/modubinascii: Update code, docs for hexlify now CPython has sep. Since CPython 3.8 the optional "sep" argument to hexlify is officially supported, so update comments in the code and the docs to reflect this. Signed-off-by: Damien George --- docs/library/ubinascii.rst | 10 ++++------ extmod/modubinascii.c | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/library/ubinascii.rst b/docs/library/ubinascii.rst index 192d34514b3d9..721b80508e31a 100644 --- a/docs/library/ubinascii.rst +++ b/docs/library/ubinascii.rst @@ -14,13 +14,11 @@ Functions .. function:: hexlify(data, [sep]) - Convert binary data to hexadecimal representation. Returns bytes string. - - .. admonition:: Difference to CPython - :class: attention + Convert the bytes in the *data* object to a hexadecimal representation. + Returns a bytes object. - If additional argument, *sep* is supplied, it is used as a separator - between hexadecimal values. + If the additional argument *sep* is supplied it is used as a separator + between hexadecimal values. .. function:: unhexlify(data) diff --git a/extmod/modubinascii.c b/extmod/modubinascii.c index 1d4c72b24b45a..9e4f86fbd2356 100644 --- a/extmod/modubinascii.c +++ b/extmod/modubinascii.c @@ -34,8 +34,8 @@ #if MICROPY_PY_UBINASCII STATIC mp_obj_t mod_binascii_hexlify(size_t n_args, const mp_obj_t *args) { - // Second argument is for an extension to allow a separator to be used - // between values. + // First argument is the data to convert. + // Second argument is an optional separator to be used between values. const char *sep = NULL; mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[0], &bufinfo, MP_BUFFER_READ); From dc1fd4df7360d101952426dd0d1574d3971e50f5 Mon Sep 17 00:00:00 2001 From: Oliver Joos Date: Fri, 27 Nov 2020 11:05:39 +0100 Subject: [PATCH 159/179] tests/extmod: Add test to try and mount a block device directly. Mounting a bdev directly tries to auto-detect the filesystem and if none is found an OSError(19,) should be raised. The fourth parameter of readblocks() and writeblocks() must be optional to support ports with MICROPY_VFS_FAT=1. Otherwise mounting a bdev may fail because looking for a FATFS will call readblocks() with only 3 parameters. --- tests/extmod/vfs_lfs_mount.py | 26 +++++++++++++++++++------- tests/extmod/vfs_lfs_mount.py.exp | 2 ++ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/tests/extmod/vfs_lfs_mount.py b/tests/extmod/vfs_lfs_mount.py index 3d8cec6075c86..bea8a2723ac5b 100644 --- a/tests/extmod/vfs_lfs_mount.py +++ b/tests/extmod/vfs_lfs_mount.py @@ -16,12 +16,12 @@ class RAMBlockDevice: def __init__(self, blocks): self.data = bytearray(blocks * self.ERASE_BLOCK_SIZE) - def readblocks(self, block, buf, off): + def readblocks(self, block, buf, off=0): addr = block * self.ERASE_BLOCK_SIZE + off for i in range(len(buf)): buf[i] = self.data[addr + i] - def writeblocks(self, block, buf, off): + def writeblocks(self, block, buf, off=0): addr = block * self.ERASE_BLOCK_SIZE + off for i in range(len(buf)): self.data[addr + i] = buf[i] @@ -35,9 +35,17 @@ def ioctl(self, op, arg): return 0 -def test(bdev, vfs_class): +def test(vfs_class): print("test", vfs_class) + bdev = RAMBlockDevice(30) + + # mount bdev unformatted + try: + uos.mount(bdev, "/lfs") + except Exception as er: + print(repr(er)) + # mkfs vfs_class.mkfs(bdev) @@ -84,12 +92,16 @@ def test(bdev, vfs_class): # umount uos.umount("/lfs") + # mount bdev again + uos.mount(bdev, "/lfs") + + # umount + uos.umount("/lfs") + # clear imported modules usys.modules.clear() -bdev = RAMBlockDevice(30) - # initialise path import usys @@ -98,5 +110,5 @@ def test(bdev, vfs_class): usys.path.append("") # run tests -test(bdev, uos.VfsLfs1) -test(bdev, uos.VfsLfs2) +test(uos.VfsLfs1) +test(uos.VfsLfs2) diff --git a/tests/extmod/vfs_lfs_mount.py.exp b/tests/extmod/vfs_lfs_mount.py.exp index aa654ebe05f12..68561b4807350 100644 --- a/tests/extmod/vfs_lfs_mount.py.exp +++ b/tests/extmod/vfs_lfs_mount.py.exp @@ -1,4 +1,5 @@ test +OSError(19,) hello from lfs package hello from lfs @@ -6,6 +7,7 @@ lfsmod2.py: print("hello from lfs") OSError(30,) test +OSError(19,) hello from lfs package hello from lfs From a13d1b50c93802b9ce6be8dac0fec53545656ef7 Mon Sep 17 00:00:00 2001 From: Oliver Joos Date: Fri, 27 Nov 2020 11:09:16 +0100 Subject: [PATCH 160/179] extmod/vfs: Raise OSError(ENODEV) if mounting bdev without a filesystem. This commit prevents uos.mount() from raising an AttributeError. vfs_autodetect() is supposed to return an object that has a "mount" method, so if no filesystem is found it should raise an OSError(ENODEV) and not return the bdev itself which has no "mount" method. --- extmod/vfs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extmod/vfs.c b/extmod/vfs.c index 3cb7af1b434f9..7dca59d351b03 100644 --- a/extmod/vfs.c +++ b/extmod/vfs.c @@ -191,7 +191,8 @@ STATIC mp_obj_t mp_vfs_autodetect(mp_obj_t bdev_obj) { return mp_fat_vfs_type.make_new(&mp_fat_vfs_type, 1, 0, &bdev_obj); #endif - return bdev_obj; + // no filesystem found + mp_raise_OSError(MP_ENODEV); } mp_obj_t mp_vfs_mount(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { From 20f8ce19825d1a976f8d61e79e661e67509681bd Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Sun, 6 Dec 2020 20:28:21 +0200 Subject: [PATCH 161/179] stm32/pyb_can: Add ability to calculate CAN bit timing from baudrate. Calculate the bit timing from baudrate if provided, allowing sample point override. This makes it a lot easier to make CAN work between different MCUs with different clocks, prescalers etc. Tested on F4, F7 and H7 Y/V variants. --- docs/library/pyb.CAN.rst | 7 ++++- ports/stm32/pyb_can.c | 62 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/docs/library/pyb.CAN.rst b/docs/library/pyb.CAN.rst index 8078e29e0cc8d..649bcda108fcc 100644 --- a/docs/library/pyb.CAN.rst +++ b/docs/library/pyb.CAN.rst @@ -49,7 +49,7 @@ Class Methods Methods ------- -.. method:: CAN.init(mode, extframe=False, prescaler=100, *, sjw=1, bs1=6, bs2=8, auto_restart=False) +.. method:: CAN.init(mode, extframe=False, prescaler=100, *, sjw=1, bs1=6, bs2=8, auto_restart=False, baudrate=0, sample_point=75) Initialise the CAN bus with the given parameters: @@ -67,6 +67,11 @@ Methods - *auto_restart* sets whether the controller will automatically try and restart communications after entering the bus-off state; if this is disabled then :meth:`~CAN.restart()` can be used to leave the bus-off state + - *baudrate* if a baudrate other than 0 is provided, this function will try to automatically + calculate a CAN bit-timing (overriding *prescaler*, *bs1* and *bs2*) that satisfies both + the baudrate and the desired *sample_point*. + - *sample_point* given in a percentage of the bit time, the *sample_point* specifies the position + of the last bit sample with respect to the whole bit time. The default *sample_point* is 75%. The time quanta tq is the basic unit of time for the CAN bus. tq is the CAN prescaler value divided by PCLK1 (the frequency of internal peripheral bus 1); diff --git a/ports/stm32/pyb_can.c b/ports/stm32/pyb_can.c index def66481cbdc8..3e55069ab9ac3 100644 --- a/ports/stm32/pyb_can.c +++ b/ports/stm32/pyb_can.c @@ -140,9 +140,41 @@ STATIC void pyb_can_print(const mp_print_t *print, mp_obj_t self_in, mp_print_ki } } +STATIC uint32_t pyb_can_get_source_freq() { + uint32_t can_kern_clk = 0; + + // Find CAN kernel clock + #if defined(STM32H7) + switch (__HAL_RCC_GET_FDCAN_SOURCE()) { + case RCC_FDCANCLKSOURCE_HSE: + can_kern_clk = HSE_VALUE; + break; + case RCC_FDCANCLKSOURCE_PLL: { + PLL1_ClocksTypeDef pll1_clocks; + HAL_RCCEx_GetPLL1ClockFreq(&pll1_clocks); + can_kern_clk = pll1_clocks.PLL1_Q_Frequency; + break; + } + case RCC_FDCANCLKSOURCE_PLL2: { + PLL2_ClocksTypeDef pll2_clocks; + HAL_RCCEx_GetPLL2ClockFreq(&pll2_clocks); + can_kern_clk = pll2_clocks.PLL2_Q_Frequency; + break; + } + } + #else // F4 and F7 and assume other MCUs too. + // CAN1/CAN2/CAN3 on APB1 use GetPCLK1Freq, alternatively use the following: + // can_kern_clk = ((HSE_VALUE / osc_config.PLL.PLLM ) * osc_config.PLL.PLLN) / + // (osc_config.PLL.PLLQ * clk_init.AHBCLKDivider * clk_init.APB1CLKDivider); + can_kern_clk = HAL_RCC_GetPCLK1Freq(); + #endif + + return can_kern_clk; +} + // init(mode, extframe=False, prescaler=100, *, sjw=1, bs1=6, bs2=8) STATIC mp_obj_t pyb_can_init_helper(pyb_can_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_mode, ARG_extframe, ARG_prescaler, ARG_sjw, ARG_bs1, ARG_bs2, ARG_auto_restart }; + enum { ARG_mode, ARG_extframe, ARG_prescaler, ARG_sjw, ARG_bs1, ARG_bs2, ARG_auto_restart, ARG_baudrate, ARG_sample_point }; static const mp_arg_t allowed_args[] = { { MP_QSTR_mode, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = CAN_MODE_NORMAL} }, { MP_QSTR_extframe, MP_ARG_BOOL, {.u_bool = false} }, @@ -151,6 +183,8 @@ STATIC mp_obj_t pyb_can_init_helper(pyb_can_obj_t *self, size_t n_args, const mp { MP_QSTR_bs1, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = CAN_DEFAULT_BS1} }, { MP_QSTR_bs2, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = CAN_DEFAULT_BS2} }, { MP_QSTR_auto_restart, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_baudrate, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_sample_point, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 75} }, // 75% sampling point }; // parse args @@ -162,6 +196,32 @@ STATIC mp_obj_t pyb_can_init_helper(pyb_can_obj_t *self, size_t n_args, const mp // set the CAN configuration values memset(&self->can, 0, sizeof(self->can)); + // Calculate CAN bit timing from baudrate if provided + if (args[ARG_baudrate].u_int != 0) { + uint32_t baudrate = args[ARG_baudrate].u_int; + uint32_t sampoint = args[ARG_sample_point].u_int; + uint32_t can_kern_clk = pyb_can_get_source_freq(); + bool timing_found = false; + + // The following max values work on all MCUs for classical CAN. + for (int brp = 1; brp < 512 && !timing_found; brp++) { + for (int bs1 = 1; bs1 < 16 && !timing_found; bs1++) { + for (int bs2 = 1; bs2 < 8 && !timing_found; bs2++) { + if ((baudrate == (can_kern_clk / (brp * (1 + bs1 + bs2)))) && + ((sampoint * 10) == (((1 + bs1) * 1000) / (1 + bs1 + bs2)))) { + args[ARG_bs1].u_int = bs1; + args[ARG_bs2].u_int = bs2; + args[ARG_prescaler].u_int = brp; + timing_found = true; + } + } + } + } + if (!timing_found) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("couldn't match baudrate and sample point")); + } + } + // init CAN (if it fails, it's because the port doesn't exist) if (!can_init(self, args[ARG_mode].u_int, args[ARG_prescaler].u_int, args[ARG_sjw].u_int, args[ARG_bs1].u_int, args[ARG_bs2].u_int, args[ARG_auto_restart].u_bool)) { From 32d76e5de66237eba456975095f998434413467a Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Tue, 8 Dec 2020 00:22:17 +0200 Subject: [PATCH 162/179] stm32/system_stm32: Enable DBGMCU in low-power modes for debug builds. --- ports/stm32/system_stm32.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/stm32/system_stm32.c b/ports/stm32/system_stm32.c index 00b7213869eae..2160e0ac994c6 100644 --- a/ports/stm32/system_stm32.c +++ b/ports/stm32/system_stm32.c @@ -425,6 +425,11 @@ void SystemClock_Config(void) { HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK); NVIC_SetPriority(SysTick_IRQn, NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, TICK_INT_PRIORITY, 0)); #endif + + #if defined(STM32H7) && !defined(NDEBUG) + // Enable the Debug Module in low-power modes. + DBGMCU->CR |= (DBGMCU_CR_DBG_SLEEPD1 | DBGMCU_CR_DBG_STOPD1 | DBGMCU_CR_DBG_STANDBYD1); + #endif } #endif From b603066bc2d272d05033705922e515d50318e735 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Tue, 8 Dec 2020 01:55:50 +0200 Subject: [PATCH 163/179] stm32/sdram: Add SDRAM enter/leave self-refresh mode functions. These functions enable SDRAM data retention in stop mode. Example usage, in mpconfigboard.h: #define MICROPY_BOARD_ENTER_STOP sdram_enter_low_power(); #define MICROPY_BOARD_LEAVE_STOP sdram_leave_low_power(); --- ports/stm32/sdram.c | 22 ++++++++++++++++++++-- ports/stm32/sdram.h | 2 ++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/ports/stm32/sdram.c b/ports/stm32/sdram.c index 0aae74e116b16..e8892b57463a4 100644 --- a/ports/stm32/sdram.c +++ b/ports/stm32/sdram.c @@ -49,8 +49,7 @@ #ifdef FMC_SDRAM_BANK -static void sdram_init_seq(SDRAM_HandleTypeDef - *hsdram, FMC_SDRAM_CommandTypeDef *command); +static void sdram_init_seq(SDRAM_HandleTypeDef *hsdram, FMC_SDRAM_CommandTypeDef *command); extern void __fatal_error(const char *msg); bool sdram_init(void) { @@ -254,6 +253,25 @@ static void sdram_init_seq(SDRAM_HandleTypeDef #endif } +void sdram_enter_low_power(void) { + // Enter self-refresh mode. + // In self-refresh mode the SDRAM retains data with external clocking. + FMC_SDRAM_DEVICE->SDCMR |= (FMC_SDRAM_CMD_SELFREFRESH_MODE | // Command Mode + FMC_SDRAM_CMD_TARGET_BANK | // Command Target + (0 << 5U) | // Auto Refresh Number -1 + (0 << 9U)); // Mode Register Definition +} + +void sdram_leave_low_power(void) { + // Exit self-refresh mode. + // Self-refresh mode is exited when the device is accessed or the mode bits are + // set to Normal mode, so technically it's not necessary to call this functions. + FMC_SDRAM_DEVICE->SDCMR |= (FMC_SDRAM_CMD_NORMAL_MODE | // Command Mode + FMC_SDRAM_CMD_TARGET_BANK | // Command Target + (0 << 5U) | // Auto Refresh Number - 1 + (0 << 9U)); // Mode Register Definition +} + bool sdram_test(bool fast) { uint8_t const pattern = 0xaa; uint8_t const antipattern = 0x55; diff --git a/ports/stm32/sdram.h b/ports/stm32/sdram.h index 9b4b4fb83e202..773a30802f87f 100644 --- a/ports/stm32/sdram.h +++ b/ports/stm32/sdram.h @@ -11,5 +11,7 @@ bool sdram_init(void); void *sdram_start(void); void *sdram_end(void); +void sdram_enter_low_power(void); +void sdram_leave_low_power(void); bool sdram_test(bool fast); #endif // __SDRAM_H__ From 80883a82c0dd40178933eb8a46b2877cb72725b2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 17 Dec 2020 12:23:09 +1100 Subject: [PATCH 164/179] stm32/adc: Deselect VBAT after reading to prevent battery drain. Signed-off-by: Damien George --- ports/stm32/adc.c | 8 ++--- ports/stm32/adc.h | 31 ++++++++++++++++++++ ports/stm32/boards/stm32f4xx_hal_conf_base.h | 1 + ports/stm32/boards/stm32f7xx_hal_conf_base.h | 1 + ports/stm32/machine_adc.c | 8 ++++- 5 files changed, 43 insertions(+), 6 deletions(-) diff --git a/ports/stm32/adc.c b/ports/stm32/adc.c index 3793ef5ff28a2..5fa22d7c78d3a 100644 --- a/ports/stm32/adc.c +++ b/ports/stm32/adc.c @@ -380,16 +380,14 @@ STATIC uint32_t adc_config_and_read_channel(ADC_HandleTypeDef *adcHandle, uint32 adc_config_channel(adcHandle, channel); uint32_t raw_value = adc_read_channel(adcHandle); - #if defined(STM32F4) || defined(STM32F7) // ST docs say that (at least on STM32F42x and STM32F43x), VBATE must // be disabled when TSVREFE is enabled for TEMPSENSOR and VREFINT // conversions to work. VBATE is enabled by the above call to read // the channel, and here we disable VBATE so a subsequent call for // TEMPSENSOR or VREFINT works correctly. - if (channel == ADC_CHANNEL_VBAT) { - ADC->CCR &= ~ADC_CCR_VBATE; - } - #endif + // It's also good to disable the VBAT switch to prevent battery drain, + // so disable it for all MCUs. + adc_deselect_vbat(adcHandle->Instance, channel); return raw_value; } diff --git a/ports/stm32/adc.h b/ports/stm32/adc.h index 4ae6022bc3362..6f2e61e099449 100644 --- a/ports/stm32/adc.h +++ b/ports/stm32/adc.h @@ -29,4 +29,35 @@ extern const mp_obj_type_t pyb_adc_type; extern const mp_obj_type_t pyb_adc_all_type; +#if defined(ADC_CHANNEL_VBAT) + +static inline void adc_deselect_vbat(ADC_TypeDef *adc, uint32_t channel) { + (void)adc; + + if (channel == ADC_CHANNEL_VBAT) { + ADC_Common_TypeDef *adc_common; + + #if defined(STM32F0) || defined(STM32WB) + adc_common = ADC1_COMMON; + #elif defined(STM32F4) || defined(STM32L4) + adc_common = ADC_COMMON_REGISTER(0); + #elif defined(STM32F7) + adc_common = ADC123_COMMON; + #elif defined(STM32H7) + adc_common = adc == ADC3 ? ADC3_COMMON : ADC12_COMMON; + #endif + + adc_common->CCR &= ~LL_ADC_PATH_INTERNAL_VBAT; + } +} + +#else + +static inline void adc_deselect_vbat(ADC_TypeDef *adc, uint32_t channel) { + (void)adc; + (void)channel; +} + +#endif + #endif // MICROPY_INCLUDED_STM32_ADC_H diff --git a/ports/stm32/boards/stm32f4xx_hal_conf_base.h b/ports/stm32/boards/stm32f4xx_hal_conf_base.h index 8d8bb8f4eeccc..91f064835e03d 100644 --- a/ports/stm32/boards/stm32f4xx_hal_conf_base.h +++ b/ports/stm32/boards/stm32f4xx_hal_conf_base.h @@ -53,6 +53,7 @@ #include "stm32f4xx_hal_uart.h" #include "stm32f4xx_hal_usart.h" #include "stm32f4xx_hal_wwdg.h" +#include "stm32f4xx_ll_adc.h" #include "stm32f4xx_ll_rtc.h" // Enable various HAL modules diff --git a/ports/stm32/boards/stm32f7xx_hal_conf_base.h b/ports/stm32/boards/stm32f7xx_hal_conf_base.h index 83a144f8fe7f0..1a3fca3ac86a8 100644 --- a/ports/stm32/boards/stm32f7xx_hal_conf_base.h +++ b/ports/stm32/boards/stm32f7xx_hal_conf_base.h @@ -53,6 +53,7 @@ #include "stm32f7xx_hal_uart.h" #include "stm32f7xx_hal_usart.h" #include "stm32f7xx_hal_wwdg.h" +#include "stm32f7xx_ll_adc.h" #include "stm32f7xx_ll_rtc.h" // Enable various HAL modules diff --git a/ports/stm32/machine_adc.c b/ports/stm32/machine_adc.c index 9c20f0f954989..d9e5a64da5533 100644 --- a/ports/stm32/machine_adc.c +++ b/ports/stm32/machine_adc.c @@ -26,6 +26,7 @@ #include "py/runtime.h" #include "py/mphal.h" +#include "adc.h" #if defined(STM32F0) || defined(STM32H7) || defined(STM32L0) || defined(STM32L4) || defined(STM32WB) #define ADC_V2 (1) @@ -335,10 +336,15 @@ STATIC uint32_t adc_config_and_read_u16(ADC_TypeDef *adc, uint32_t channel, uint return 0xffff; } + // Select, configure and read the channel. adc_config_channel(adc, channel, sample_time); uint32_t raw = adc_read_channel(adc); + + // If VBAT was sampled then deselect it to prevent battery drain. + adc_deselect_vbat(adc, channel); + + // Scale raw reading to 16 bit value using a Taylor expansion (for bits <= 16). uint32_t bits = adc_get_bits(adc); - // Scale raw reading to 16 bit value using a Taylor expansion (for 8 <= bits <= 16) #if defined(STM32H7) if (bits < 8) { // For 6 and 7 bits From 061cb1a73a4ecbf69a4e036053664b4f84754b34 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 17 Dec 2020 15:09:53 +1100 Subject: [PATCH 165/179] stm32/main: Do extended readblocks call when auto-detecting littlefs. When littlefs is enabled extended reading must be supported, and using this function to read the first block for auto-detection is more efficient (a smaller read) and does not require a cached SPI-flash read. Signed-off-by: Damien George --- ports/stm32/main.c | 13 +++++++------ ports/stm32/storage.c | 7 +++++++ ports/stm32/storage.h | 1 + 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/ports/stm32/main.c b/ports/stm32/main.c index d00c2ec713f61..fb10b96501e1d 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -164,17 +164,18 @@ MP_NOINLINE STATIC bool init_flash_fs(uint reset_mode) { // Default block device to entire flash storage mp_obj_t bdev = MP_OBJ_FROM_PTR(&pyb_flash_obj); + int ret; + #if MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2 // Try to detect the block device used for the main filesystem, based on the first block - uint8_t buf[FLASH_BLOCK_SIZE]; - storage_read_blocks(buf, FLASH_PART1_START_BLOCK, 1); - + uint8_t buf[64]; + ret = storage_readblocks_ext(buf, 0, 0, sizeof(buf)); mp_int_t len = -1; #if MICROPY_VFS_LFS1 - if (memcmp(&buf[40], "littlefs", 8) == 0) { + if (ret == 0 && memcmp(&buf[40], "littlefs", 8) == 0) { // LFS1 lfs1_superblock_t *superblock = (void *)&buf[12]; uint32_t block_size = lfs1_fromle32(superblock->d.block_size); @@ -184,7 +185,7 @@ MP_NOINLINE STATIC bool init_flash_fs(uint reset_mode) { #endif #if MICROPY_VFS_LFS2 - if (memcmp(&buf[8], "littlefs", 8) == 0) { + if (ret == 0 && memcmp(&buf[8], "littlefs", 8) == 0) { // LFS2 lfs2_superblock_t *superblock = (void *)&buf[20]; uint32_t block_size = lfs2_fromle32(superblock->block_size); @@ -203,7 +204,7 @@ MP_NOINLINE STATIC bool init_flash_fs(uint reset_mode) { // Try to mount the flash on "/flash" and chdir to it for the boot-up directory. mp_obj_t mount_point = MP_OBJ_NEW_QSTR(MP_QSTR__slash_flash); - int ret = mp_vfs_mount_and_chdir_protected(bdev, mount_point); + ret = mp_vfs_mount_and_chdir_protected(bdev, mount_point); if (ret == -MP_ENODEV && bdev == MP_OBJ_FROM_PTR(&pyb_flash_obj) && reset_mode != 3) { // No filesystem, bdev is still the default (so didn't detect a possibly corrupt littlefs), diff --git a/ports/stm32/storage.c b/ports/stm32/storage.c index c8805d682986b..6581860ff3ffb 100644 --- a/ports/stm32/storage.c +++ b/ports/stm32/storage.c @@ -250,6 +250,13 @@ int storage_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_bl #define PYB_FLASH_NATIVE_BLOCK_SIZE (FLASH_BLOCK_SIZE) #endif +#if defined(MICROPY_HW_BDEV_READBLOCKS_EXT) +// Size of blocks is PYB_FLASH_NATIVE_BLOCK_SIZE +int storage_readblocks_ext(uint8_t *dest, uint32_t block, uint32_t offset, uint32_t len) { + return MICROPY_HW_BDEV_READBLOCKS_EXT(dest, block, offset, len); +} +#endif + typedef struct _pyb_flash_obj_t { mp_obj_base_t base; uint32_t start; // in bytes diff --git a/ports/stm32/storage.h b/ports/stm32/storage.h index 490fc4a09b8ac..73058aad6b393 100644 --- a/ports/stm32/storage.h +++ b/ports/stm32/storage.h @@ -50,6 +50,7 @@ bool storage_write_block(const uint8_t *src, uint32_t block); // these return 0 on success, negative errno on error int storage_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks); int storage_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks); +int storage_readblocks_ext(uint8_t *dest, uint32_t block, uint32_t offset, uint32_t len); int32_t flash_bdev_ioctl(uint32_t op, uint32_t arg); bool flash_bdev_readblock(uint8_t *dest, uint32_t block); From e43a74a4db484ac1bfef191a8aa19b58b519efc6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 17 Dec 2020 16:59:54 +1100 Subject: [PATCH 166/179] drivers/memory/spiflash: Add MICROPY_HW_SPIFLASH_ENABLE_CACHE option. This only needs to be enabled if a board uses FAT FS on external SPI flash. When disabled (and using external SPI flash) 4k of RAM can be saved. Signed-off-by: Damien George --- drivers/memory/spiflash.c | 4 ++++ drivers/memory/spiflash.h | 6 ++++++ ports/stm32/boards/PYBD_SF2/mpconfigboard.h | 1 + ports/stm32/boards/STM32F769DISC/mpconfigboard.h | 1 + ports/stm32/boards/STM32L476DISC/mpconfigboard.h | 1 + ports/stm32/mpconfigboard_common.h | 8 ++++++++ ports/stm32/spibdev.c | 6 ++++++ 7 files changed, 27 insertions(+) diff --git a/drivers/memory/spiflash.c b/drivers/memory/spiflash.c index e870d39f5f9cb..bb15bd6252519 100644 --- a/drivers/memory/spiflash.c +++ b/drivers/memory/spiflash.c @@ -287,6 +287,8 @@ int mp_spiflash_write(mp_spiflash_t *self, uint32_t addr, size_t len, const uint /******************************************************************************/ // Interface functions that use the cache +#if MICROPY_HW_SPIFLASH_ENABLE_CACHE + void mp_spiflash_cached_read(mp_spiflash_t *self, uint32_t addr, size_t len, uint8_t *dest) { if (len == 0) { return; @@ -509,3 +511,5 @@ int mp_spiflash_cached_write(mp_spiflash_t *self, uint32_t addr, size_t len, con mp_spiflash_release_bus(self); return 0; } + +#endif // MICROPY_HW_SPIFLASH_ENABLE_CACHE diff --git a/drivers/memory/spiflash.h b/drivers/memory/spiflash.h index 96dfdeeab66bf..c4162ff21cdc6 100644 --- a/drivers/memory/spiflash.h +++ b/drivers/memory/spiflash.h @@ -38,6 +38,7 @@ enum { struct _mp_spiflash_t; +#if MICROPY_HW_SPIFLASH_ENABLE_CACHE // A cache must be provided by the user in the config struct. The same cache // struct can be shared by multiple SPI flash instances. typedef struct _mp_spiflash_cache_t { @@ -45,6 +46,7 @@ typedef struct _mp_spiflash_cache_t { struct _mp_spiflash_t *user; // current user of buf, for shared use uint32_t block; // current block stored in buf; 0xffffffff if invalid } mp_spiflash_cache_t; +#endif typedef struct _mp_spiflash_config_t { uint32_t bus_kind; @@ -59,7 +61,9 @@ typedef struct _mp_spiflash_config_t { const mp_qspi_proto_t *proto; } u_qspi; } bus; + #if MICROPY_HW_SPIFLASH_ENABLE_CACHE mp_spiflash_cache_t *cache; // can be NULL if cache functions not used + #endif } mp_spiflash_config_t; typedef struct _mp_spiflash_t { @@ -75,9 +79,11 @@ int mp_spiflash_erase_block(mp_spiflash_t *self, uint32_t addr); void mp_spiflash_read(mp_spiflash_t *self, uint32_t addr, size_t len, uint8_t *dest); int mp_spiflash_write(mp_spiflash_t *self, uint32_t addr, size_t len, const uint8_t *src); +#if MICROPY_HW_SPIFLASH_ENABLE_CACHE // These functions use the cache (which must already be configured) void mp_spiflash_cache_flush(mp_spiflash_t *self); void mp_spiflash_cached_read(mp_spiflash_t *self, uint32_t addr, size_t len, uint8_t *dest); int mp_spiflash_cached_write(mp_spiflash_t *self, uint32_t addr, size_t len, const uint8_t *src); +#endif #endif // MICROPY_INCLUDED_DRIVERS_MEMORY_SPIFLASH_H diff --git a/ports/stm32/boards/PYBD_SF2/mpconfigboard.h b/ports/stm32/boards/PYBD_SF2/mpconfigboard.h index f6e130eb57eaf..29164ac11c909 100644 --- a/ports/stm32/boards/PYBD_SF2/mpconfigboard.h +++ b/ports/stm32/boards/PYBD_SF2/mpconfigboard.h @@ -77,6 +77,7 @@ void board_sleep(int value); // SPI flash #1, block device config extern const struct _mp_spiflash_config_t spiflash_config; extern struct _spi_bdev_t spi_bdev; +#define MICROPY_HW_SPIFLASH_ENABLE_CACHE (1) #define MICROPY_HW_BDEV_IOCTL(op, arg) ( \ (op) == BDEV_IOCTL_NUM_BLOCKS ? (MICROPY_HW_SPIFLASH_SIZE_BITS / 8 / FLASH_BLOCK_SIZE) : \ (op) == BDEV_IOCTL_INIT ? spi_bdev_ioctl(&spi_bdev, (op), (uint32_t)&spiflash_config) : \ diff --git a/ports/stm32/boards/STM32F769DISC/mpconfigboard.h b/ports/stm32/boards/STM32F769DISC/mpconfigboard.h index e1fe93bb0ed44..843b987cee714 100644 --- a/ports/stm32/boards/STM32F769DISC/mpconfigboard.h +++ b/ports/stm32/boards/STM32F769DISC/mpconfigboard.h @@ -40,6 +40,7 @@ extern const struct _mp_spiflash_config_t spiflash_config; extern struct _spi_bdev_t spi_bdev; #if !USE_QSPI_XIP #define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (0) +#define MICROPY_HW_SPIFLASH_ENABLE_CACHE (1) #define MICROPY_HW_BDEV_IOCTL(op, arg) ( \ (op) == BDEV_IOCTL_NUM_BLOCKS ? ((1 << MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2) / 8 / FLASH_BLOCK_SIZE) : \ (op) == BDEV_IOCTL_INIT ? spi_bdev_ioctl(&spi_bdev, (op), (uint32_t)&spiflash_config) : \ diff --git a/ports/stm32/boards/STM32L476DISC/mpconfigboard.h b/ports/stm32/boards/STM32L476DISC/mpconfigboard.h index 2831187531178..ad62f761e55f3 100644 --- a/ports/stm32/boards/STM32L476DISC/mpconfigboard.h +++ b/ports/stm32/boards/STM32L476DISC/mpconfigboard.h @@ -22,6 +22,7 @@ void STM32L476DISC_board_early_init(void); // block device config for SPI flash extern const struct _mp_spiflash_config_t spiflash_config; extern struct _spi_bdev_t spi_bdev; +#define MICROPY_HW_SPIFLASH_ENABLE_CACHE (1) #define MICROPY_HW_BDEV_IOCTL(op, arg) ( \ (op) == BDEV_IOCTL_NUM_BLOCKS ? (MICROPY_HW_SPIFLASH_SIZE_BITS / 8 / FLASH_BLOCK_SIZE) : \ (op) == BDEV_IOCTL_INIT ? spi_bdev_ioctl(&spi_bdev, (op), (uint32_t)&spiflash_config) : \ diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index 8890abc59bbe1..60fbc35fcbae0 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -286,6 +286,14 @@ #define MICROPY_HW_BDEV_WRITEBLOCK flash_bdev_writeblock #endif +// Whether to enable caching for external SPI flash, to allow block writes that are +// smaller than the native page-erase size of the SPI flash, eg when FAT FS is used. +// Enabling this enables spi_bdev_readblocks() and spi_bdev_writeblocks() functions, +// and requires a valid mp_spiflash_config_t.cache pointer. +#ifndef MICROPY_HW_SPIFLASH_ENABLE_CACHE +#define MICROPY_HW_SPIFLASH_ENABLE_CACHE (0) +#endif + // Enable the storage sub-system if a block device is defined #if defined(MICROPY_HW_BDEV_IOCTL) #define MICROPY_HW_ENABLE_STORAGE (1) diff --git a/ports/stm32/spibdev.c b/ports/stm32/spibdev.c index 05c8819a7737f..5090b43b1cb92 100644 --- a/ports/stm32/spibdev.c +++ b/ports/stm32/spibdev.c @@ -41,19 +41,23 @@ int32_t spi_bdev_ioctl(spi_bdev_t *bdev, uint32_t op, uint32_t arg) { return 0; case BDEV_IOCTL_IRQ_HANDLER: + #if MICROPY_HW_SPIFLASH_ENABLE_CACHE if ((bdev->spiflash.flags & 1) && HAL_GetTick() - bdev->flash_tick_counter_last_write >= 1000) { mp_spiflash_cache_flush(&bdev->spiflash); led_state(PYB_LED_RED, 0); // indicate a clean cache with LED off } + #endif return 0; case BDEV_IOCTL_SYNC: + #if MICROPY_HW_SPIFLASH_ENABLE_CACHE if (bdev->spiflash.flags & 1) { uint32_t basepri = raise_irq_pri(IRQ_PRI_FLASH); // prevent cache flushing and USB access mp_spiflash_cache_flush(&bdev->spiflash); led_state(PYB_LED_RED, 0); // indicate a clean cache with LED off restore_irq_pri(basepri); } + #endif return 0; case BDEV_IOCTL_BLOCK_ERASE: { @@ -66,6 +70,7 @@ int32_t spi_bdev_ioctl(spi_bdev_t *bdev, uint32_t op, uint32_t arg) { return -MP_EINVAL; } +#if MICROPY_HW_SPIFLASH_ENABLE_CACHE int spi_bdev_readblocks(spi_bdev_t *bdev, uint8_t *dest, uint32_t block_num, uint32_t num_blocks) { uint32_t basepri = raise_irq_pri(IRQ_PRI_FLASH); // prevent cache flushing and USB access mp_spiflash_cached_read(&bdev->spiflash, block_num * FLASH_BLOCK_SIZE, num_blocks * FLASH_BLOCK_SIZE, dest); @@ -85,6 +90,7 @@ int spi_bdev_writeblocks(spi_bdev_t *bdev, const uint8_t *src, uint32_t block_nu return ret; } +#endif // MICROPY_HW_SPIFLASH_ENABLE_CACHE int spi_bdev_readblocks_raw(spi_bdev_t *bdev, uint8_t *dest, uint32_t block_num, uint32_t block_offset, uint32_t num_bytes) { uint32_t basepri = raise_irq_pri(IRQ_PRI_FLASH); // prevent cache flushing and USB access From e715a8fb9bf9c572b805aca4682048183c6eb97c Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 17 Dec 2020 21:59:58 +1100 Subject: [PATCH 167/179] stm32/boards/PYBD_SF2: Disable SPIFLASH_ENABLE_CACHE for mboot builds. Mboot builds do not use the external SPI flash in caching mode, and explicitly disabling it saves RAM and a small bit of flash. Signed-off-by: Damien George --- ports/stm32/boards/PYBD_SF2/bdev.c | 6 ++++++ ports/stm32/boards/PYBD_SF2/mpconfigboard.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/ports/stm32/boards/PYBD_SF2/bdev.c b/ports/stm32/boards/PYBD_SF2/bdev.c index 6c5ff721ec707..d9bec93c27e40 100644 --- a/ports/stm32/boards/PYBD_SF2/bdev.c +++ b/ports/stm32/boards/PYBD_SF2/bdev.c @@ -27,8 +27,10 @@ #include "storage.h" #include "qspi.h" +#if MICROPY_HW_SPIFLASH_ENABLE_CACHE // Shared cache for first and second SPI block devices STATIC mp_spiflash_cache_t spi_bdev_cache; +#endif // First external SPI flash uses software QSPI interface @@ -45,7 +47,9 @@ const mp_spiflash_config_t spiflash_config = { .bus_kind = MP_SPIFLASH_BUS_QSPI, .bus.u_qspi.data = (void*)&soft_qspi_bus, .bus.u_qspi.proto = &mp_soft_qspi_proto, + #if MICROPY_HW_SPIFLASH_ENABLE_CACHE .cache = &spi_bdev_cache, + #endif }; spi_bdev_t spi_bdev; @@ -56,7 +60,9 @@ const mp_spiflash_config_t spiflash2_config = { .bus_kind = MP_SPIFLASH_BUS_QSPI, .bus.u_qspi.data = NULL, .bus.u_qspi.proto = &qspi_proto, + #if MICROPY_HW_SPIFLASH_ENABLE_CACHE .cache = &spi_bdev_cache, + #endif }; spi_bdev_t spi_bdev2; diff --git a/ports/stm32/boards/PYBD_SF2/mpconfigboard.h b/ports/stm32/boards/PYBD_SF2/mpconfigboard.h index 29164ac11c909..45d968f353609 100644 --- a/ports/stm32/boards/PYBD_SF2/mpconfigboard.h +++ b/ports/stm32/boards/PYBD_SF2/mpconfigboard.h @@ -77,7 +77,9 @@ void board_sleep(int value); // SPI flash #1, block device config extern const struct _mp_spiflash_config_t spiflash_config; extern struct _spi_bdev_t spi_bdev; +#if !BUILDING_MBOOT #define MICROPY_HW_SPIFLASH_ENABLE_CACHE (1) +#endif #define MICROPY_HW_BDEV_IOCTL(op, arg) ( \ (op) == BDEV_IOCTL_NUM_BLOCKS ? (MICROPY_HW_SPIFLASH_SIZE_BITS / 8 / FLASH_BLOCK_SIZE) : \ (op) == BDEV_IOCTL_INIT ? spi_bdev_ioctl(&spi_bdev, (op), (uint32_t)&spiflash_config) : \ From 505a1853b9490246fbb95850ad8dae6d1b49d002 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 18 Dec 2020 13:48:26 +1100 Subject: [PATCH 168/179] teensy: Fix build errors and warnings and enable -Werror. Changes are: - Remove include of stm32's adc.h because it was recently changed and is no longer compatible with teensy (and not used anyway). - Remove define of __disable_irq in mpconfigport.h because it was clashing with an equivalent definition in core/mk20dx128.h. - Add -Werror to CFLAGS, and change -std=gnu99 to -std=c99. Signed-off-by: Damien George --- ports/teensy/Makefile | 2 +- ports/teensy/modpyb.c | 1 - ports/teensy/mpconfigport.h | 6 +----- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/ports/teensy/Makefile b/ports/teensy/Makefile index f2623eabd3dc3..d3978718e0095 100644 --- a/ports/teensy/Makefile +++ b/ports/teensy/Makefile @@ -38,7 +38,7 @@ INC += -I$(TOP)/ports/stm32 INC += -I$(BUILD) INC += -Icore -CFLAGS = $(INC) -Wall -Wpointer-arith -std=gnu99 -nostdlib $(CFLAGS_CORTEX_M4) +CFLAGS = $(INC) -Wall -Wpointer-arith -Werror -std=c99 -nostdlib $(CFLAGS_CORTEX_M4) LDFLAGS = -nostdlib -T mk20dx256.ld -msoft-float -mfloat-abi=soft ifeq ($(USE_ARDUINO_TOOLCHAIN),1) diff --git a/ports/teensy/modpyb.c b/ports/teensy/modpyb.c index f4384a885426e..6671b3abdce0e 100644 --- a/ports/teensy/modpyb.c +++ b/ports/teensy/modpyb.c @@ -44,7 +44,6 @@ #include "usrsw.h" #include "rng.h" #include "uart.h" -#include "adc.h" #include "storage.h" #include "sdcard.h" #include "accel.h" diff --git a/ports/teensy/mpconfigport.h b/ports/teensy/mpconfigport.h index 24269c3b981e6..c00da5ed9e498 100644 --- a/ports/teensy/mpconfigport.h +++ b/ports/teensy/mpconfigport.h @@ -72,10 +72,6 @@ typedef long mp_off_t; // value from disable_irq back to enable_irq. If you really need // to know the machine-specific values, see irq.h. -#ifndef __disable_irq -#define __disable_irq() __asm__ volatile ("CPSID i"); -#endif - __attribute__((always_inline)) static inline uint32_t __get_PRIMASK(void) { uint32_t result; __asm volatile ("MRS %0, primask" : "=r" (result)); @@ -92,7 +88,7 @@ __attribute__((always_inline)) static inline void enable_irq(mp_uint_t state) { __attribute__((always_inline)) static inline mp_uint_t disable_irq(void) { mp_uint_t state = __get_PRIMASK(); - __disable_irq(); + __asm__ volatile ("CPSID i"); return state; } From 108183fcc01f722d17e373f91f392a2a60ac787a Mon Sep 17 00:00:00 2001 From: stijn Date: Tue, 15 Dec 2020 11:54:34 +0100 Subject: [PATCH 169/179] tests/misc/sys_settrace: Make test output independent of invoked path. The original logic of reducing a full path to a relative one assumes "tests/misc" is in the filename which is limited in usage: it never works for CPython on Windows since that will use a backslash as path separator, and also won't work when the filename is a path not relative to the tests directory which happens for example in the common case of running "./run-tests -d misc". Fix all cases by printing only the bare filename, which requires them all to start with sys_settrace_ hence the renaming. --- tests/misc/sys_settrace_features.py | 8 +- tests/misc/sys_settrace_generator.py | 4 +- tests/misc/sys_settrace_generator.py.exp | 276 +++++++++--------- tests/misc/sys_settrace_loop.py | 4 +- tests/misc/sys_settrace_loop.py.exp | 92 +++--- ...ace_generic.py => sys_settrace_generic.py} | 6 +- ...e_importme.py => sys_settrace_importme.py} | 0 7 files changed, 195 insertions(+), 195 deletions(-) rename tests/misc/sys_settrace_subdir/{trace_generic.py => sys_settrace_generic.py} (91%) rename tests/misc/sys_settrace_subdir/{trace_importme.py => sys_settrace_importme.py} (100%) diff --git a/tests/misc/sys_settrace_features.py b/tests/misc/sys_settrace_features.py index d315ea69730c0..e1b1a059dad6f 100644 --- a/tests/misc/sys_settrace_features.py +++ b/tests/misc/sys_settrace_features.py @@ -20,8 +20,8 @@ def print_stacktrace(frame, level=0): " ", frame.f_globals["__name__"], frame.f_code.co_name, - # reduce full path to some pseudo-relative - "misc" + "".join(frame.f_code.co_filename.split("tests/misc")[-1:]), + # Keep just the filename. + "sys_settrace_" + frame.f_code.co_filename.split("sys_settrace_")[-1], frame.f_lineno, ) ) @@ -95,9 +95,9 @@ def do_tests(): print("Who loves the sun?") print("Not every-", factorial(3)) - from sys_settrace_subdir import trace_generic + from sys_settrace_subdir import sys_settrace_generic - trace_generic.run_tests() + sys_settrace_generic.run_tests() return diff --git a/tests/misc/sys_settrace_generator.py b/tests/misc/sys_settrace_generator.py index 4ace0f50e81b5..43065df4ae9be 100644 --- a/tests/misc/sys_settrace_generator.py +++ b/tests/misc/sys_settrace_generator.py @@ -17,8 +17,8 @@ def print_stacktrace(frame, level=0): " ", frame.f_globals["__name__"], frame.f_code.co_name, - # reduce full path to some pseudo-relative - "misc" + "".join(frame.f_code.co_filename.split("tests/misc")[-1:]), + # Keep just the filename. + "sys_settrace_" + frame.f_code.co_filename.split("sys_settrace_")[-1], frame.f_lineno, ) ) diff --git a/tests/misc/sys_settrace_generator.py.exp b/tests/misc/sys_settrace_generator.py.exp index de9d0bf1c36a6..a83450afe380c 100644 --- a/tests/misc/sys_settrace_generator.py.exp +++ b/tests/misc/sys_settrace_generator.py.exp @@ -1,195 +1,195 @@ ### trace_handler::main event: call - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:41 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:41 + 1: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:42 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:42 + 1: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:48 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:48 + 1: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:49 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:49 + 1: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:50 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:50 + 1: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:52 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:52 + 1: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: call - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:42 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:52 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:42 + 1: @__main__:test_generator => sys_settrace_generator.py:52 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:43 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:52 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:43 + 1: @__main__:test_generator => sys_settrace_generator.py:52 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: return - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:43 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:52 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:43 + 1: @__main__:test_generator => sys_settrace_generator.py:52 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:56 + 1: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: call - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:43 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:43 + 1: @__main__:test_generator => sys_settrace_generator.py:56 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:43 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:43 + 1: @__main__:test_generator => sys_settrace_generator.py:56 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:44 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:44 + 1: @__main__:test_generator => sys_settrace_generator.py:56 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: return - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:44 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:44 + 1: @__main__:test_generator => sys_settrace_generator.py:56 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: call - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:44 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:44 + 1: @__main__:test_generator => sys_settrace_generator.py:56 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:44 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:44 + 1: @__main__:test_generator => sys_settrace_generator.py:56 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:45 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:45 + 1: @__main__:test_generator => sys_settrace_generator.py:56 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: return - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:45 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:45 + 1: @__main__:test_generator => sys_settrace_generator.py:56 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: call - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:45 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:45 + 1: @__main__:test_generator => sys_settrace_generator.py:56 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:45 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:45 + 1: @__main__:test_generator => sys_settrace_generator.py:56 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:46 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:46 + 1: @__main__:test_generator => sys_settrace_generator.py:56 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: return - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:46 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:46 + 1: @__main__:test_generator => sys_settrace_generator.py:56 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: exception - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:56 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:56 + 1: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:58 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:58 + 1: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:59 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:59 + 1: @__main__: => sys_settrace_generator.py:69 test_generator 7 8 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:61 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:61 + 1: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:62 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:62 + 1: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:63 + 1: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: call - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:42 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:42 + 1: @__main__:test_generator => sys_settrace_generator.py:63 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:43 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:43 + 1: @__main__:test_generator => sys_settrace_generator.py:63 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: return - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:43 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:43 + 1: @__main__:test_generator => sys_settrace_generator.py:63 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:63 + 1: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:64 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:64 + 1: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:63 + 1: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: call - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:43 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:43 + 1: @__main__:test_generator => sys_settrace_generator.py:63 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:43 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:43 + 1: @__main__:test_generator => sys_settrace_generator.py:63 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:44 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:44 + 1: @__main__:test_generator => sys_settrace_generator.py:63 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: return - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:44 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:44 + 1: @__main__:test_generator => sys_settrace_generator.py:63 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:63 + 1: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:64 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:64 + 1: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:63 + 1: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: call - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:44 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:44 + 1: @__main__:test_generator => sys_settrace_generator.py:63 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:44 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:44 + 1: @__main__:test_generator => sys_settrace_generator.py:63 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:45 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:45 + 1: @__main__:test_generator => sys_settrace_generator.py:63 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: return - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:45 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:45 + 1: @__main__:test_generator => sys_settrace_generator.py:63 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:63 + 1: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:64 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:64 + 1: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:63 + 1: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: call - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:45 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:45 + 1: @__main__:test_generator => sys_settrace_generator.py:63 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:45 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:45 + 1: @__main__:test_generator => sys_settrace_generator.py:63 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:46 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:46 + 1: @__main__:test_generator => sys_settrace_generator.py:63 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: return - 0: @__main__:make_gen => miscmisc/sys_settrace_generator.py:46 - 1: @__main__:test_generator => miscmisc/sys_settrace_generator.py:63 - 2: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:make_gen => sys_settrace_generator.py:46 + 1: @__main__:test_generator => sys_settrace_generator.py:63 + 2: @__main__: => sys_settrace_generator.py:69 ### trace_handler::main event: line - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:65 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:65 + 1: @__main__: => sys_settrace_generator.py:69 7 ### trace_handler::main event: return - 0: @__main__:test_generator => miscmisc/sys_settrace_generator.py:65 - 1: @__main__: => miscmisc/sys_settrace_generator.py:69 + 0: @__main__:test_generator => sys_settrace_generator.py:65 + 1: @__main__: => sys_settrace_generator.py:69 Total traces executed: 54 diff --git a/tests/misc/sys_settrace_loop.py b/tests/misc/sys_settrace_loop.py index 06d0dc17bf77b..1186bd91a0726 100644 --- a/tests/misc/sys_settrace_loop.py +++ b/tests/misc/sys_settrace_loop.py @@ -17,8 +17,8 @@ def print_stacktrace(frame, level=0): " ", frame.f_globals["__name__"], frame.f_code.co_name, - # reduce full path to some pseudo-relative - "misc" + "".join(frame.f_code.co_filename.split("tests/misc")[-1:]), + # Keep just the filename. + "sys_settrace_" + frame.f_code.co_filename.split("sys_settrace_")[-1], frame.f_lineno, ) ) diff --git a/tests/misc/sys_settrace_loop.py.exp b/tests/misc/sys_settrace_loop.py.exp index f56f98fae01f3..ff9ef577c550c 100644 --- a/tests/misc/sys_settrace_loop.py.exp +++ b/tests/misc/sys_settrace_loop.py.exp @@ -1,72 +1,72 @@ ### trace_handler::main event: call - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:41 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:41 + 1: @__main__: => sys_settrace_loop.py:58 ### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:43 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:43 + 1: @__main__: => sys_settrace_loop.py:58 ### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:44 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:44 + 1: @__main__: => sys_settrace_loop.py:58 ### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:45 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:45 + 1: @__main__: => sys_settrace_loop.py:58 ### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:44 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:44 + 1: @__main__: => sys_settrace_loop.py:58 ### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:45 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:45 + 1: @__main__: => sys_settrace_loop.py:58 ### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:44 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:44 + 1: @__main__: => sys_settrace_loop.py:58 ### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:45 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:45 + 1: @__main__: => sys_settrace_loop.py:58 ### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:44 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:44 + 1: @__main__: => sys_settrace_loop.py:58 ### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:45 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:45 + 1: @__main__: => sys_settrace_loop.py:58 ### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:46 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:46 + 1: @__main__: => sys_settrace_loop.py:58 test_for_loop 3 ### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:49 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:49 + 1: @__main__: => sys_settrace_loop.py:58 ### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:50 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:50 + 1: @__main__: => sys_settrace_loop.py:58 ### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:51 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:51 + 1: @__main__: => sys_settrace_loop.py:58 ### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:53 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:53 + 1: @__main__: => sys_settrace_loop.py:58 ### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:52 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:52 + 1: @__main__: => sys_settrace_loop.py:58 ### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:53 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:53 + 1: @__main__: => sys_settrace_loop.py:58 ### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:52 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:52 + 1: @__main__: => sys_settrace_loop.py:58 ### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:53 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:53 + 1: @__main__: => sys_settrace_loop.py:58 ### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:52 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:52 + 1: @__main__: => sys_settrace_loop.py:58 ### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:53 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:53 + 1: @__main__: => sys_settrace_loop.py:58 ### trace_handler::main event: line - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:54 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:54 + 1: @__main__: => sys_settrace_loop.py:58 test_while_loop 3 ### trace_handler::main event: return - 0: @__main__:test_loop => miscmisc/sys_settrace_loop.py:54 - 1: @__main__: => miscmisc/sys_settrace_loop.py:58 + 0: @__main__:test_loop => sys_settrace_loop.py:54 + 1: @__main__: => sys_settrace_loop.py:58 Total traces executed: 23 diff --git a/tests/misc/sys_settrace_subdir/trace_generic.py b/tests/misc/sys_settrace_subdir/sys_settrace_generic.py similarity index 91% rename from tests/misc/sys_settrace_subdir/trace_generic.py rename to tests/misc/sys_settrace_subdir/sys_settrace_generic.py index 111a9d19ff30d..a60ca955d7c7c 100644 --- a/tests/misc/sys_settrace_subdir/trace_generic.py +++ b/tests/misc/sys_settrace_subdir/sys_settrace_generic.py @@ -41,10 +41,10 @@ def test_lambda(): # import def test_import(): - from sys_settrace_subdir import trace_importme + from sys_settrace_subdir import sys_settrace_importme - trace_importme.dummy() - trace_importme.saysomething() + sys_settrace_importme.dummy() + sys_settrace_importme.saysomething() # class diff --git a/tests/misc/sys_settrace_subdir/trace_importme.py b/tests/misc/sys_settrace_subdir/sys_settrace_importme.py similarity index 100% rename from tests/misc/sys_settrace_subdir/trace_importme.py rename to tests/misc/sys_settrace_subdir/sys_settrace_importme.py From 069557edef84a4f37ad52ae93838129da8e5fe77 Mon Sep 17 00:00:00 2001 From: stijn Date: Tue, 15 Dec 2020 12:03:42 +0100 Subject: [PATCH 170/179] tests/misc/sys_settrace_features.py: Fix running with non-dflt encoding. Notably git-cmd which comes with git installations on Windows alters the encoding resulting in CPython tracing encodings/cp1252.py calls. --- tests/misc/sys_settrace_features.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/misc/sys_settrace_features.py b/tests/misc/sys_settrace_features.py index e1b1a059dad6f..8a38b7534f7df 100644 --- a/tests/misc/sys_settrace_features.py +++ b/tests/misc/sys_settrace_features.py @@ -60,8 +60,9 @@ def trace_tick_handler_bob(frame, event, arg): def trace_tick_handler(frame, event, arg): # Ignore CPython specific helpers. + to_ignore = ["importlib", "zipimport", "encodings"] frame_name = frame.f_globals["__name__"] - if frame_name.find("importlib") != -1 or frame_name.find("zipimport") != -1: + if any(name in frame_name for name in to_ignore): return print("### trace_handler::main event:", event) From f42a190247661e1e5aa18e0ce93c627fd7676d8e Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Mon, 21 Dec 2020 17:31:12 +1100 Subject: [PATCH 171/179] extmod/nimble: Reset NimBLE BSS in mp_bluetooth_init. Without this fix, each time service registration happened it would do an increasingly large malloc() for service state. See https://github.com/apache/mynewt-nimble/issues/896. --- extmod/nimble/modbluetooth_nimble.c | 35 ++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 613e325a953f5..843886d009d12 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -476,11 +476,27 @@ void mp_bluetooth_nimble_port_shutdown(void) { #endif // !MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY +void nimble_reset_gatts_bss(void) { + // NimBLE assumes that service registration only ever happens once, so + // we need to reset service registration state from a previous stack startup. + // These variables are defined in ble_hs.c and are only ever incremented + // (during service registration) and never reset. + // See https://github.com/apache/mynewt-nimble/issues/896 + extern uint16_t ble_hs_max_attrs; + extern uint16_t ble_hs_max_services; + extern uint16_t ble_hs_max_client_configs; + ble_hs_max_attrs = 0; + ble_hs_max_services = 0; + ble_hs_max_client_configs = 0; +} + int mp_bluetooth_init(void) { DEBUG_printf("mp_bluetooth_init\n"); // Clean up if necessary. mp_bluetooth_deinit(); + nimble_reset_gatts_bss(); + mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_STARTING; ble_hs_cfg.reset_cb = reset_cb; @@ -775,6 +791,15 @@ int mp_bluetooth_gatts_register_service_begin(bool append) { if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } + + if (append) { + // Don't support append yet (modbluetooth.c doesn't support it yet anyway). + // TODO: This should be possible with NimBLE. + return MP_EOPNOTSUPP; + } + + nimble_reset_gatts_bss(); + int ret = ble_gatts_reset(); if (ret != 0) { return ble_hs_err_to_errno(ret); @@ -787,13 +812,11 @@ int mp_bluetooth_gatts_register_service_begin(bool append) { ble_svc_gap_init(); ble_svc_gatt_init(); - if (!append) { - // Unref any previous service definitions. - for (size_t i = 0; i < MP_STATE_PORT(bluetooth_nimble_root_pointers)->n_services; ++i) { - MP_STATE_PORT(bluetooth_nimble_root_pointers)->services[i] = NULL; - } - MP_STATE_PORT(bluetooth_nimble_root_pointers)->n_services = 0; + // Unref any previous service definitions. + for (size_t i = 0; i < MP_STATE_PORT(bluetooth_nimble_root_pointers)->n_services; ++i) { + MP_STATE_PORT(bluetooth_nimble_root_pointers)->services[i] = NULL; } + MP_STATE_PORT(bluetooth_nimble_root_pointers)->n_services = 0; return 0; } From f7aafc0628f2008d015b32b0c0253a13f748d436 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 22 Dec 2020 13:06:16 +1100 Subject: [PATCH 172/179] extmod/nimble: Don't assert on save-IRK failure. --- extmod/nimble/modbluetooth_nimble.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 843886d009d12..9ceeab7d7b6c6 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -227,7 +227,9 @@ STATIC int load_irk(void) { } DEBUG_printf("load_irk: Saving new IRK.\n"); if (!mp_bluetooth_gap_on_set_secret(SECRET_TYPE_OUR_IRK, key, sizeof(key), rand_irk, 16)) { - return BLE_HS_EINVAL; + // Code that doesn't implement pairing/bonding won't support set/get secret. + // So they'll just get the default fixed IRK. + return 0; } DEBUG_printf("load_irk: Applying new IRK.\n"); rc = ble_hs_pvcy_set_our_irk(rand_irk); From ec3b500bcb59f4e0d99b7ff4cadb9189918192e6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 23 Sep 2020 15:53:46 +1000 Subject: [PATCH 173/179] py,extmod: Add core cmake rule files. Signed-off-by: Damien George --- extmod/extmod.cmake | 42 +++++++++++++++ py/mkrules.cmake | 102 ++++++++++++++++++++++++++++++++++++ py/py.cmake | 124 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 268 insertions(+) create mode 100644 extmod/extmod.cmake create mode 100644 py/mkrules.cmake create mode 100644 py/py.cmake diff --git a/extmod/extmod.cmake b/extmod/extmod.cmake new file mode 100644 index 0000000000000..bff023479a2bd --- /dev/null +++ b/extmod/extmod.cmake @@ -0,0 +1,42 @@ +# CMake fragment for MicroPython extmod component + +set(MPY_EXTMOD_DIR "${MPY_DIR}/extmod") +set(OOFATFS_DIR "${MPY_DIR}/lib/oofatfs") + +set(SOURCE_EXTMOD + ${MPY_EXTMOD_DIR}/machine_i2c.c + ${MPY_EXTMOD_DIR}/machine_mem.c + ${MPY_EXTMOD_DIR}/machine_pulse.c + ${MPY_EXTMOD_DIR}/machine_signal.c + ${MPY_EXTMOD_DIR}/machine_spi.c + ${MPY_EXTMOD_DIR}/modbtree.c + ${MPY_EXTMOD_DIR}/modframebuf.c + ${MPY_EXTMOD_DIR}/moduasyncio.c + ${MPY_EXTMOD_DIR}/modubinascii.c + ${MPY_EXTMOD_DIR}/moducryptolib.c + ${MPY_EXTMOD_DIR}/moductypes.c + ${MPY_EXTMOD_DIR}/moduhashlib.c + ${MPY_EXTMOD_DIR}/moduheapq.c + ${MPY_EXTMOD_DIR}/modujson.c + ${MPY_EXTMOD_DIR}/modurandom.c + ${MPY_EXTMOD_DIR}/modure.c + ${MPY_EXTMOD_DIR}/moduselect.c + ${MPY_EXTMOD_DIR}/modussl_axtls.c + ${MPY_EXTMOD_DIR}/modussl_mbedtls.c + ${MPY_EXTMOD_DIR}/modutimeq.c + ${MPY_EXTMOD_DIR}/moduwebsocket.c + ${MPY_EXTMOD_DIR}/moduzlib.c + ${MPY_EXTMOD_DIR}/modwebrepl.c + ${MPY_EXTMOD_DIR}/uos_dupterm.c + ${MPY_EXTMOD_DIR}/utime_mphal.c + ${MPY_EXTMOD_DIR}/vfs.c + ${MPY_EXTMOD_DIR}/vfs_blockdev.c + ${MPY_EXTMOD_DIR}/vfs_fat.c + ${MPY_EXTMOD_DIR}/vfs_fat_diskio.c + ${MPY_EXTMOD_DIR}/vfs_fat_file.c + ${MPY_EXTMOD_DIR}/vfs_lfs.c + ${MPY_EXTMOD_DIR}/vfs_posix.c + ${MPY_EXTMOD_DIR}/vfs_posix_file.c + ${MPY_EXTMOD_DIR}/vfs_reader.c + ${MPY_EXTMOD_DIR}/virtpin.c +) diff --git a/py/mkrules.cmake b/py/mkrules.cmake new file mode 100644 index 0000000000000..d25126e3195d2 --- /dev/null +++ b/py/mkrules.cmake @@ -0,0 +1,102 @@ +# CMake fragment for MicroPython rules + +set(MPY_PY_QSTRDEFS "${MPY_PY_DIR}/qstrdefs.h") +set(MPY_GENHDR_DIR "${CMAKE_BINARY_DIR}/genhdr") +set(MPY_MPVERSION "${MPY_GENHDR_DIR}/mpversion.h") +set(MPY_MODULEDEFS "${MPY_GENHDR_DIR}/moduledefs.h") +set(MPY_QSTR_DEFS_LAST "${MPY_GENHDR_DIR}/qstr.i.last") +set(MPY_QSTR_DEFS_SPLIT "${MPY_GENHDR_DIR}/qstr.split") +set(MPY_QSTR_DEFS_COLLECTED "${MPY_GENHDR_DIR}/qstrdefs.collected.h") +set(MPY_QSTR_DEFS_PREPROCESSED "${MPY_GENHDR_DIR}/qstrdefs.preprocessed.h") +set(MPY_QSTR_DEFS_GENERATED "${MPY_GENHDR_DIR}/qstrdefs.generated.h") +set(MPY_FROZEN_CONTENT "${CMAKE_BINARY_DIR}/frozen_content.c") + +target_sources(${MICROPYTHON_TARGET} PRIVATE + ${MPY_MPVERSION} + ${MPY_QSTR_DEFS_GENERATED} + ${MPY_FROZEN_CONTENT} +) + +# Command to force the build of another command + +add_custom_command( + OUTPUT FORCE_BUILD + COMMENT "" + COMMAND echo -n +) + +# Generate mpversion.h + +add_custom_command( + OUTPUT ${MPY_MPVERSION} + COMMAND ${CMAKE_COMMAND} -E make_directory ${MPY_GENHDR_DIR} + COMMAND python3 ${MPY_DIR}/py/makeversionhdr.py ${MPY_MPVERSION} + DEPENDS FORCE_BUILD +) + +# Generate moduledefs.h +# This is currently hard-coded to support modarray.c only, because makemoduledefs.py doesn't support absolute paths + +add_custom_command( + OUTPUT ${MPY_MODULEDEFS} + COMMAND python3 ${MPY_PY_DIR}/makemoduledefs.py --vpath="." ../../../py/modarray.c > ${MPY_MODULEDEFS} + DEPENDS ${MPY_MPVERSION} + ${SOURCE_QSTR} +) + +# Generate qstrs + +# If any of the dependencies in this rule change then the C-preprocessor step must be run. +# It only needs to be passed the list of SOURCE_QSTR files that have changed since it was +# last run, but it looks like it's not possible to specify that with cmake. +add_custom_command( + OUTPUT ${MPY_QSTR_DEFS_LAST} + COMMAND ${CMAKE_C_COMPILER} -E \$\(C_INCLUDES\) \$\(C_FLAGS\) -DNO_QSTR ${SOURCE_QSTR} > ${MPY_GENHDR_DIR}/qstr.i.last + DEPENDS ${MPY_MODULEDEFS} + ${SOURCE_QSTR} + VERBATIM +) + +add_custom_command( + OUTPUT ${MPY_QSTR_DEFS_SPLIT} + COMMAND python3 ${MPY_DIR}/py/makeqstrdefs.py split qstr ${MPY_GENHDR_DIR}/qstr.i.last ${MPY_GENHDR_DIR}/qstr _ + COMMAND touch ${MPY_QSTR_DEFS_SPLIT} + DEPENDS ${MPY_QSTR_DEFS_LAST} + VERBATIM +) + +add_custom_command( + OUTPUT ${MPY_QSTR_DEFS_COLLECTED} + COMMAND python3 ${MPY_DIR}/py/makeqstrdefs.py cat qstr _ ${MPY_GENHDR_DIR}/qstr ${MPY_QSTR_DEFS_COLLECTED} + DEPENDS ${MPY_QSTR_DEFS_SPLIT} + VERBATIM +) + +add_custom_command( + OUTPUT ${MPY_QSTR_DEFS_PREPROCESSED} + COMMAND cat ${MPY_PY_QSTRDEFS} ${MPY_QSTR_DEFS_COLLECTED} | sed "s/^Q(.*)/\"&\"/" | ${CMAKE_C_COMPILER} -E \$\(C_INCLUDES\) \$\(C_FLAGS\) - | sed "s/^\\\"\\(Q(.*)\\)\\\"/\\1/" > ${MPY_QSTR_DEFS_PREPROCESSED} + DEPENDS ${MPY_QSTR_DEFS_COLLECTED} + VERBATIM +) + +add_custom_command( + OUTPUT ${MPY_QSTR_DEFS_GENERATED} + COMMAND python3 ${MPY_PY_DIR}/makeqstrdata.py ${MPY_QSTR_DEFS_PREPROCESSED} > ${MPY_QSTR_DEFS_GENERATED} + DEPENDS ${MPY_QSTR_DEFS_PREPROCESSED} + VERBATIM +) + +# Build frozen code + +target_compile_options(${MICROPYTHON_TARGET} PUBLIC + -DMICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool + -DMICROPY_MODULE_FROZEN_MPY=\(1\) +) + +add_custom_command( + OUTPUT ${MPY_FROZEN_CONTENT} + COMMAND python3 ${MPY_DIR}/tools/makemanifest.py -o ${MPY_FROZEN_CONTENT} -v "MPY_DIR=${MPY_DIR}" -v "PORT_DIR=${MPY_PORT_DIR}" -b "${CMAKE_BINARY_DIR}" -f${MPY_CROSS_FLAGS} ${FROZEN_MANIFEST} + DEPENDS FORCE_BUILD + ${MPY_QSTR_DEFS_GENERATED} + VERBATIM +) diff --git a/py/py.cmake b/py/py.cmake new file mode 100644 index 0000000000000..37f4c29c02971 --- /dev/null +++ b/py/py.cmake @@ -0,0 +1,124 @@ +# CMake fragment for MicroPython core py component + +set(MPY_PY_DIR "${MPY_DIR}/py") + +# All py/ source files +set(SOURCE_PY + ${MPY_PY_DIR}/argcheck.c + ${MPY_PY_DIR}/asmarm.c + ${MPY_PY_DIR}/asmbase.c + ${MPY_PY_DIR}/asmthumb.c + ${MPY_PY_DIR}/asmx64.c + ${MPY_PY_DIR}/asmx86.c + ${MPY_PY_DIR}/asmxtensa.c + ${MPY_PY_DIR}/bc.c + ${MPY_PY_DIR}/binary.c + ${MPY_PY_DIR}/builtinevex.c + ${MPY_PY_DIR}/builtinhelp.c + ${MPY_PY_DIR}/builtinimport.c + ${MPY_PY_DIR}/compile.c + ${MPY_PY_DIR}/emitbc.c + ${MPY_PY_DIR}/emitcommon.c + ${MPY_PY_DIR}/emitglue.c + ${MPY_PY_DIR}/emitinlinethumb.c + ${MPY_PY_DIR}/emitinlinextensa.c + ${MPY_PY_DIR}/emitnarm.c + ${MPY_PY_DIR}/emitnthumb.c + ${MPY_PY_DIR}/emitnx64.c + ${MPY_PY_DIR}/emitnx86.c + ${MPY_PY_DIR}/emitnxtensa.c + ${MPY_PY_DIR}/emitnxtensawin.c + ${MPY_PY_DIR}/formatfloat.c + ${MPY_PY_DIR}/frozenmod.c + ${MPY_PY_DIR}/gc.c + ${MPY_PY_DIR}/lexer.c + ${MPY_PY_DIR}/malloc.c + ${MPY_PY_DIR}/map.c + ${MPY_PY_DIR}/modarray.c + ${MPY_PY_DIR}/modbuiltins.c + ${MPY_PY_DIR}/modcmath.c + ${MPY_PY_DIR}/modcollections.c + ${MPY_PY_DIR}/modgc.c + ${MPY_PY_DIR}/modio.c + ${MPY_PY_DIR}/modmath.c + ${MPY_PY_DIR}/modmicropython.c + ${MPY_PY_DIR}/modstruct.c + ${MPY_PY_DIR}/modsys.c + ${MPY_PY_DIR}/modthread.c + ${MPY_PY_DIR}/moduerrno.c + ${MPY_PY_DIR}/mpprint.c + ${MPY_PY_DIR}/mpstate.c + ${MPY_PY_DIR}/mpz.c + ${MPY_PY_DIR}/nativeglue.c + ${MPY_PY_DIR}/nlr.c + ${MPY_PY_DIR}/nlrpowerpc.c + ${MPY_PY_DIR}/nlrsetjmp.c + ${MPY_PY_DIR}/nlrthumb.c + ${MPY_PY_DIR}/nlrx64.c + ${MPY_PY_DIR}/nlrx86.c + ${MPY_PY_DIR}/nlrxtensa.c + ${MPY_PY_DIR}/obj.c + ${MPY_PY_DIR}/objarray.c + ${MPY_PY_DIR}/objattrtuple.c + ${MPY_PY_DIR}/objbool.c + ${MPY_PY_DIR}/objboundmeth.c + ${MPY_PY_DIR}/objcell.c + ${MPY_PY_DIR}/objclosure.c + ${MPY_PY_DIR}/objcomplex.c + ${MPY_PY_DIR}/objdeque.c + ${MPY_PY_DIR}/objdict.c + ${MPY_PY_DIR}/objenumerate.c + ${MPY_PY_DIR}/objexcept.c + ${MPY_PY_DIR}/objfilter.c + ${MPY_PY_DIR}/objfloat.c + ${MPY_PY_DIR}/objfun.c + ${MPY_PY_DIR}/objgenerator.c + ${MPY_PY_DIR}/objgetitemiter.c + ${MPY_PY_DIR}/objint.c + ${MPY_PY_DIR}/objint_longlong.c + ${MPY_PY_DIR}/objint_mpz.c + ${MPY_PY_DIR}/objlist.c + ${MPY_PY_DIR}/objmap.c + ${MPY_PY_DIR}/objmodule.c + ${MPY_PY_DIR}/objnamedtuple.c + ${MPY_PY_DIR}/objnone.c + ${MPY_PY_DIR}/objobject.c + ${MPY_PY_DIR}/objpolyiter.c + ${MPY_PY_DIR}/objproperty.c + ${MPY_PY_DIR}/objrange.c + ${MPY_PY_DIR}/objreversed.c + ${MPY_PY_DIR}/objset.c + ${MPY_PY_DIR}/objsingleton.c + ${MPY_PY_DIR}/objslice.c + ${MPY_PY_DIR}/objstr.c + ${MPY_PY_DIR}/objstringio.c + ${MPY_PY_DIR}/objstrunicode.c + ${MPY_PY_DIR}/objtuple.c + ${MPY_PY_DIR}/objtype.c + ${MPY_PY_DIR}/objzip.c + ${MPY_PY_DIR}/opmethods.c + ${MPY_PY_DIR}/pairheap.c + ${MPY_PY_DIR}/parse.c + ${MPY_PY_DIR}/parsenum.c + ${MPY_PY_DIR}/parsenumbase.c + ${MPY_PY_DIR}/persistentcode.c + ${MPY_PY_DIR}/profile.c + ${MPY_PY_DIR}/pystack.c + ${MPY_PY_DIR}/qstr.c + ${MPY_PY_DIR}/reader.c + ${MPY_PY_DIR}/repl.c + ${MPY_PY_DIR}/ringbuf.c + ${MPY_PY_DIR}/runtime.c + ${MPY_PY_DIR}/runtime_utils.c + ${MPY_PY_DIR}/scheduler.c + ${MPY_PY_DIR}/scope.c + ${MPY_PY_DIR}/sequence.c + ${MPY_PY_DIR}/showbc.c + ${MPY_PY_DIR}/smallint.c + ${MPY_PY_DIR}/stackctrl.c + ${MPY_PY_DIR}/stream.c + ${MPY_PY_DIR}/unicode.c + ${MPY_PY_DIR}/vm.c + ${MPY_PY_DIR}/vstr.c + ${MPY_PY_DIR}/warning.c +) From 40ac9e2b2eab3ccf9c49c3c1c002fa4fd83a81ab Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 23 Sep 2020 15:55:33 +1000 Subject: [PATCH 174/179] esp32: Update to support IDF v4.1. Signed-off-by: Damien George --- ports/esp32/machine_uart.c | 24 +++++++++++++----------- ports/esp32/modnetwork.c | 7 +++---- ports/esp32/modsocket.c | 3 +-- ports/esp32/uart.c | 1 + 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/ports/esp32/machine_uart.c b/ports/esp32/machine_uart.c index c334e694b21b0..30fafc11201e7 100644 --- a/ports/esp32/machine_uart.c +++ b/ports/esp32/machine_uart.c @@ -36,6 +36,8 @@ #include "py/mperrno.h" #include "modmachine.h" +#define UART_LINE_INV_MASK (0xff) + typedef struct _machine_uart_obj_t { mp_obj_base_t base; uart_port_t uart_num; @@ -68,28 +70,28 @@ STATIC void machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_pri if (self->invert) { mp_printf(print, ", invert="); uint32_t invert_mask = self->invert; - if (invert_mask & UART_INVERSE_TXD) { + if (invert_mask & UART_SIGNAL_TXD_INV) { mp_printf(print, "INV_TX"); - invert_mask &= ~UART_INVERSE_TXD; + invert_mask &= ~UART_SIGNAL_TXD_INV; if (invert_mask) { mp_printf(print, "|"); } } - if (invert_mask & UART_INVERSE_RXD) { + if (invert_mask & UART_SIGNAL_RXD_INV) { mp_printf(print, "INV_RX"); - invert_mask &= ~UART_INVERSE_RXD; + invert_mask &= ~UART_SIGNAL_RXD_INV; if (invert_mask) { mp_printf(print, "|"); } } - if (invert_mask & UART_INVERSE_RTS) { + if (invert_mask & UART_SIGNAL_RTS_INV) { mp_printf(print, "INV_RTS"); - invert_mask &= ~UART_INVERSE_RTS; + invert_mask &= ~UART_SIGNAL_RTS_INV; if (invert_mask) { mp_printf(print, "|"); } } - if (invert_mask & UART_INVERSE_CTS) { + if (invert_mask & UART_SIGNAL_CTS_INV) { mp_printf(print, "INV_CTS"); } } @@ -380,10 +382,10 @@ STATIC const mp_rom_map_elem_t machine_uart_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, { MP_ROM_QSTR(MP_QSTR_sendbreak), MP_ROM_PTR(&machine_uart_sendbreak_obj) }, - { MP_ROM_QSTR(MP_QSTR_INV_TX), MP_ROM_INT(UART_INVERSE_TXD) }, - { MP_ROM_QSTR(MP_QSTR_INV_RX), MP_ROM_INT(UART_INVERSE_RXD) }, - { MP_ROM_QSTR(MP_QSTR_INV_RTS), MP_ROM_INT(UART_INVERSE_RTS) }, - { MP_ROM_QSTR(MP_QSTR_INV_CTS), MP_ROM_INT(UART_INVERSE_CTS) }, + { MP_ROM_QSTR(MP_QSTR_INV_TX), MP_ROM_INT(UART_SIGNAL_TXD_INV) }, + { MP_ROM_QSTR(MP_QSTR_INV_RX), MP_ROM_INT(UART_SIGNAL_RXD_INV) }, + { MP_ROM_QSTR(MP_QSTR_INV_RTS), MP_ROM_INT(UART_SIGNAL_RTS_INV) }, + { MP_ROM_QSTR(MP_QSTR_INV_CTS), MP_ROM_INT(UART_SIGNAL_CTS_INV) }, }; STATIC MP_DEFINE_CONST_DICT(machine_uart_locals_dict, machine_uart_locals_dict_table); diff --git a/ports/esp32/modnetwork.c b/ports/esp32/modnetwork.c index 44263350409fc..d91fc936a83b3 100644 --- a/ports/esp32/modnetwork.c +++ b/ports/esp32/modnetwork.c @@ -45,7 +45,6 @@ #include "esp_wifi.h" #include "esp_log.h" #include "lwip/dns.h" -#include "tcpip_adapter.h" #include "mdns.h" #if !MICROPY_ESP_IDF_4 @@ -491,7 +490,7 @@ STATIC mp_obj_t esp_ifconfig(size_t n_args, const mp_obj_t *args) { tcpip_adapter_ip_info_t info; tcpip_adapter_dns_info_t dns_info; tcpip_adapter_get_ip_info(self->if_id, &info); - tcpip_adapter_get_dns_info(self->if_id, TCPIP_ADAPTER_DNS_MAIN, &dns_info); + tcpip_adapter_get_dns_info(self->if_id, ESP_NETIF_DNS_MAIN, &dns_info); if (n_args == 1) { // get mp_obj_t tuple[4] = { @@ -526,14 +525,14 @@ STATIC mp_obj_t esp_ifconfig(size_t n_args, const mp_obj_t *args) { _esp_exceptions(e); } ESP_EXCEPTIONS(tcpip_adapter_set_ip_info(self->if_id, &info)); - ESP_EXCEPTIONS(tcpip_adapter_set_dns_info(self->if_id, TCPIP_ADAPTER_DNS_MAIN, &dns_info)); + ESP_EXCEPTIONS(tcpip_adapter_set_dns_info(self->if_id, ESP_NETIF_DNS_MAIN, &dns_info)); } else if (self->if_id == WIFI_IF_AP) { esp_err_t e = tcpip_adapter_dhcps_stop(WIFI_IF_AP); if (e != ESP_OK && e != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STOPPED) { _esp_exceptions(e); } ESP_EXCEPTIONS(tcpip_adapter_set_ip_info(WIFI_IF_AP, &info)); - ESP_EXCEPTIONS(tcpip_adapter_set_dns_info(WIFI_IF_AP, TCPIP_ADAPTER_DNS_MAIN, &dns_info)); + ESP_EXCEPTIONS(tcpip_adapter_set_dns_info(WIFI_IF_AP, ESP_NETIF_DNS_MAIN, &dns_info)); ESP_EXCEPTIONS(tcpip_adapter_dhcps_start(WIFI_IF_AP)); } } else { diff --git a/ports/esp32/modsocket.c b/ports/esp32/modsocket.c index 85433e575fc51..5647559a67230 100644 --- a/ports/esp32/modsocket.c +++ b/ports/esp32/modsocket.c @@ -46,7 +46,6 @@ #include "py/stream.h" #include "py/mperrno.h" #include "lib/netutils/netutils.h" -#include "tcpip_adapter.h" #include "mdns.h" #include "modnetwork.h" @@ -181,7 +180,7 @@ static int _socket_getaddrinfo3(const char *nodename, const char *servname, memcpy(nodename_no_local, nodename, nodename_len - local_len); nodename_no_local[nodename_len - local_len] = '\0'; - struct ip4_addr addr = {0}; + esp_ip4_addr_t addr = {0}; esp_err_t err = mdns_query_a(nodename_no_local, MDNS_QUERY_TIMEOUT_MS, &addr); if (err != ESP_OK) { if (err == ESP_ERR_NOT_FOUND) { diff --git a/ports/esp32/uart.c b/ports/esp32/uart.c index 381be7f4f016c..c837c8dcfe6a4 100644 --- a/ports/esp32/uart.c +++ b/ports/esp32/uart.c @@ -29,6 +29,7 @@ #include #include "driver/uart.h" +#include "soc/uart_periph.h" #include "py/runtime.h" #include "py/mphal.h" From a70761504ebf9a6d9ec553ce14445da6ce9f5a1e Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 23 Sep 2020 15:55:55 +1000 Subject: [PATCH 175/179] esp32: Add support to build using IDF with cmake. Signed-off-by: Damien George --- ports/esp32/CMakeLists.txt | 5 ++ ports/esp32/boards/sdkconfig.base | 5 ++ ports/esp32/main/CMakeLists.txt | 123 ++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 ports/esp32/CMakeLists.txt create mode 100644 ports/esp32/main/CMakeLists.txt diff --git a/ports/esp32/CMakeLists.txt b/ports/esp32/CMakeLists.txt new file mode 100644 index 0000000000000..1900313742116 --- /dev/null +++ b/ports/esp32/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.5) +set(SDKCONFIG ${CMAKE_BINARY_DIR}/sdkconfig) +set(SDKCONFIG_DEFAULTS boards/sdkconfig.base) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(micropython) diff --git a/ports/esp32/boards/sdkconfig.base b/ports/esp32/boards/sdkconfig.base index 67e2424a1246d..de6e42c8f3139 100644 --- a/ports/esp32/boards/sdkconfig.base +++ b/ports/esp32/boards/sdkconfig.base @@ -51,3 +51,8 @@ CONFIG_PPP_SUPPORT=y CONFIG_PPP_PAP_SUPPORT=y CONFIG_PPP_CHAP_SUPPORT=y CONFIG_ULP_COPROC_ENABLED=y + +# For cmake build +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" diff --git a/ports/esp32/main/CMakeLists.txt b/ports/esp32/main/CMakeLists.txt new file mode 100644 index 0000000000000..4e8b9c739655f --- /dev/null +++ b/ports/esp32/main/CMakeLists.txt @@ -0,0 +1,123 @@ +# Set location of base MicroPython directory, and this port's directory. +get_filename_component(MPY_DIR ${PROJECT_DIR}/../.. ABSOLUTE) +set(MPY_PORT_DIR ${PROJECT_DIR}) +set(MPY_BOARD_DIR ${PROJECT_DIR}/boards/GENERIC) + +# Include core source components. +include(${MPY_DIR}/py/py.cmake) +include(${MPY_DIR}/extmod/extmod.cmake) + +set(SOURCE_EXTMOD_EXTRA + ${MPY_DIR}/extmod/modonewire.c +) + +set(SOURCE_LIB + ${MPY_DIR}/lib/littlefs/lfs1.c + ${MPY_DIR}/lib/littlefs/lfs1_util.c + ${MPY_DIR}/lib/littlefs/lfs2.c + ${MPY_DIR}/lib/littlefs/lfs2_util.c + ${MPY_DIR}/lib/mbedtls_errors/mp_mbedtls_errors.c + ${MPY_DIR}/lib/mp-readline/readline.c + ${MPY_DIR}/lib/netutils/netutils.c + ${MPY_DIR}/lib/oofatfs/ff.c + ${MPY_DIR}/lib/oofatfs/ffunicode.c + ${MPY_DIR}/lib/timeutils/timeutils.c + ${MPY_DIR}/lib/utils/interrupt_char.c + ${MPY_DIR}/lib/utils/stdout_helpers.c + ${MPY_DIR}/lib/utils/sys_stdio_mphal.c + ${MPY_DIR}/lib/utils/pyexec.c +) + +set(SOURCE_DRIVERS + ${MPY_DIR}/drivers/bus/softspi.c + ${MPY_DIR}/drivers/dht/dht.c +) + +set(SOURCE_PORT + ${PROJECT_DIR}/main.c + ${PROJECT_DIR}/uart.c + ${PROJECT_DIR}/gccollect.c + ${PROJECT_DIR}/mphalport.c + ${PROJECT_DIR}/fatfs_port.c + ${PROJECT_DIR}/help.c + ${PROJECT_DIR}/modutime.c + ${PROJECT_DIR}/moduos.c + ${PROJECT_DIR}/machine_timer.c + ${PROJECT_DIR}/machine_pin.c + ${PROJECT_DIR}/machine_touchpad.c + ${PROJECT_DIR}/machine_adc.c + ${PROJECT_DIR}/machine_dac.c + ${PROJECT_DIR}/machine_i2c.c + ${PROJECT_DIR}/machine_pwm.c + ${PROJECT_DIR}/machine_uart.c + ${PROJECT_DIR}/modmachine.c + ${PROJECT_DIR}/modnetwork.c + ${PROJECT_DIR}/network_lan.c + ${PROJECT_DIR}/network_ppp.c + ${PROJECT_DIR}/mpnimbleport.c + ${PROJECT_DIR}/modsocket.c + ${PROJECT_DIR}/modesp.c + ${PROJECT_DIR}/esp32_partition.c + ${PROJECT_DIR}/esp32_rmt.c + ${PROJECT_DIR}/esp32_ulp.c + ${PROJECT_DIR}/modesp32.c + ${PROJECT_DIR}/espneopixel.c + ${PROJECT_DIR}/machine_hw_spi.c + ${PROJECT_DIR}/machine_wdt.c + ${PROJECT_DIR}/mpthreadport.c + ${PROJECT_DIR}/machine_rtc.c + ${PROJECT_DIR}/machine_sdcard.c +) + +set(SOURCE_QSTR + ${SOURCE_PY} + ${SOURCE_EXTMOD} + ${SOURCE_EXTMOD_EXTRA} + ${SOURCE_LIB} + ${SOURCE_PORT} +) + +# Register the main IDF component. +idf_component_register( + SRCS + ${SOURCE_PY} + ${SOURCE_EXTMOD} + ${SOURCE_EXTMOD_EXTRA} + ${SOURCE_LIB} + ${SOURCE_DRIVERS} + ${SOURCE_PORT} + INCLUDE_DIRS + ${MPY_DIR} + ${MPY_PORT_DIR} + ${MPY_BOARD_DIR} + ${CMAKE_BINARY_DIR} + REQUIRES + soc nvs_flash ulp sdmmc mdns app_update lwip +) + +# Define mpy-cross flags and frozen manifest +set(MPY_CROSS_FLAGS -march=xtensawin) +set(FROZEN_MANIFEST ${PROJECT_DIR}/boards/manifest.py) + +# Include the main MicroPython cmake rules. +set(MICROPYTHON_TARGET ${COMPONENT_TARGET}) +include(${MPY_DIR}/py/mkrules.cmake) + +# Set compile options for this port. +target_compile_options(${MICROPYTHON_TARGET} PUBLIC + -DMICROPY_ESP_IDF_4=1 + -DMICROPY_VFS_FAT=1 + -DMICROPY_VFS_LFS2=1 + -DFFCONF_H=\"${OOFATFS_DIR}/ffconf.h\" + -DLFS1_NO_MALLOC -DLFS1_NO_DEBUG -DLFS1_NO_WARN -DLFS1_NO_ERROR -DLFS1_NO_ASSERT + -DLFS2_NO_MALLOC -DLFS2_NO_DEBUG -DLFS2_NO_WARN -DLFS2_NO_ERROR -DLFS2_NO_ASSERT +) + +# Disable some warnings to keep the build output clean. +target_compile_options(${MICROPYTHON_TARGET} PUBLIC + -Wno-clobbered + -Wno-deprecated-declarations + -Wno-implicit-fallthrough + -Wno-missing-field-initializers + -Wno-override-init +) From 2aa94e72992a401d841f9bbfe9f6e3cb6d61e26a Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 6 Oct 2020 18:06:09 +1100 Subject: [PATCH 176/179] esp32/esp32_rmt: Don't do unnecessary check for unsigned less than zero. --- ports/esp32/esp32_rmt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index 7971ca5d1c5af..b547af5539f34 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -209,7 +209,7 @@ STATIC mp_obj_t esp32_rmt_write_pulses(size_t n_args, const mp_obj_t *pos_args, mp_obj_t pulses = args[1].u_obj; mp_uint_t start = args[2].u_int; - if (start < 0 || start > 1) { + if (start > 1) { mp_raise_ValueError(MP_ERROR_TEXT("start must be 0 or 1")); } From be4a80d6db8bcf4dc6eb1f521fc8cc5a0513d4f2 Mon Sep 17 00:00:00 2001 From: Antoine Aubert Date: Mon, 7 Dec 2020 09:52:03 +0100 Subject: [PATCH 177/179] esp32: add support for bluetooth in cmake. --- extmod/extmod.cmake | 18 ++++++++++++++++++ ports/esp32/main/CMakeLists.txt | 5 +++++ 2 files changed, 23 insertions(+) diff --git a/extmod/extmod.cmake b/extmod/extmod.cmake index bff023479a2bd..1a053ce4ebcdf 100644 --- a/extmod/extmod.cmake +++ b/extmod/extmod.cmake @@ -39,4 +39,22 @@ set(SOURCE_EXTMOD ${MPY_EXTMOD_DIR}/vfs_posix_file.c ${MPY_EXTMOD_DIR}/vfs_reader.c ${MPY_EXTMOD_DIR}/virtpin.c + ${MPY_EXTMOD_DIR}/modbluetooth.c + ${MPY_EXTMOD_DIR}/nimble/modbluetooth_nimble.c + ${MPY_EXTMOD_DIR}/nimble/hal/hal_uart.c +) + +set (INCLUDE_EXTMOD + ${IDF_PATH}/components/bt/host/nimble/nimble/nimble/host/include + ${IDF_PATH}/components/bt/host/nimble/esp-hci/include/ + ${IDF_PATH}/components/bt/host/nimble/nimble/nimble/include + ${IDF_PATH}/components/bt/host/nimble/nimble/porting/nimble/include + ${IDF_PATH}/components/bt/host/nimble/port/include + ${IDF_PATH}/components/bt/host/nimble/nimble/porting/npl/freertos/include + ${IDF_PATH}/components/bt/common/osi/include/osi + ${IDF_PATH}/components/bt/host/nimble/nimble/nimble/host/util/include + ${IDF_PATH}/components/bt/host/nimble/nimble/porting/npl/freertos/include + ${IDF_PATH}/components/bt/host/nimble/nimble/nimble/host/services/gap/include + ${IDF_PATH}/components/bt/host/nimble/nimble/nimble/host/services/gatt/include + ${IDF_PATH}/components/bt/host/nimble/nimble/nimble/transport/uart/include ) diff --git a/ports/esp32/main/CMakeLists.txt b/ports/esp32/main/CMakeLists.txt index 4e8b9c739655f..dccb6bbe44646 100644 --- a/ports/esp32/main/CMakeLists.txt +++ b/ports/esp32/main/CMakeLists.txt @@ -91,6 +91,7 @@ idf_component_register( ${MPY_PORT_DIR} ${MPY_BOARD_DIR} ${CMAKE_BINARY_DIR} + ${INCLUDE_EXTMOD} REQUIRES soc nvs_flash ulp sdmmc mdns app_update lwip ) @@ -108,6 +109,10 @@ target_compile_options(${MICROPYTHON_TARGET} PUBLIC -DMICROPY_ESP_IDF_4=1 -DMICROPY_VFS_FAT=1 -DMICROPY_VFS_LFS2=1 + -DMICROPY_PY_BLUETOOTH=1 + -DMICROPY_BLUETOOTH_NIMBLE=1 + -DMICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY=1 + -DMICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE=1 -DFFCONF_H=\"${OOFATFS_DIR}/ffconf.h\" -DLFS1_NO_MALLOC -DLFS1_NO_DEBUG -DLFS1_NO_WARN -DLFS1_NO_ERROR -DLFS1_NO_ASSERT -DLFS2_NO_MALLOC -DLFS2_NO_DEBUG -DLFS2_NO_WARN -DLFS2_NO_ERROR -DLFS2_NO_ASSERT From 1f9e7e3919172f44e18aed58eba1d4fa3954585b Mon Sep 17 00:00:00 2001 From: Antoine Aubert Date: Wed, 23 Dec 2020 22:06:18 +0100 Subject: [PATCH 178/179] nimble: overide structures only if needed. --- extmod/nimble/nimble/nimble_npl_os.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extmod/nimble/nimble/nimble_npl_os.h b/extmod/nimble/nimble/nimble_npl_os.h index d0803f7e2e81d..ee4ad84aa815f 100644 --- a/extmod/nimble/nimble/nimble_npl_os.h +++ b/extmod/nimble/nimble/nimble_npl_os.h @@ -33,6 +33,7 @@ #include // --- Configuration of NimBLE data structures -------------------------------- +#ifndef MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY // This is used at runtime to align allocations correctly. #define BLE_NPL_OS_ALIGNMENT (sizeof(uintptr_t)) @@ -80,7 +81,7 @@ struct ble_npl_mutex { struct ble_npl_sem { volatile uint16_t count; }; - +#endif // --- Called by the MicroPython port ----------------------------------------- void mp_bluetooth_nimble_os_eventq_run_all(void); From e089fa5300d44e13a41c5e339b1b4a0dcd94e04c Mon Sep 17 00:00:00 2001 From: Antoine Aubert Date: Thu, 24 Dec 2020 08:39:46 +0100 Subject: [PATCH 179/179] esp32/cmake: add a defaut sdkconfig. --- ports/esp32/sdkconfig | 1315 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1315 insertions(+) create mode 100644 ports/esp32/sdkconfig diff --git a/ports/esp32/sdkconfig b/ports/esp32/sdkconfig new file mode 100644 index 0000000000000..7b0af4f042790 --- /dev/null +++ b/ports/esp32/sdkconfig @@ -0,0 +1,1315 @@ +# +# Automatically generated file. DO NOT EDIT. +# Espressif IoT Development Framework (ESP-IDF) Project Configuration +# +CONFIG_IDF_CMAKE=y +CONFIG_IDF_TARGET="esp32" +CONFIG_IDF_TARGET_ESP32=y +CONFIG_IDF_FIRMWARE_CHIP_ID=0x0000 + +# +# SDK tool configuration +# +CONFIG_SDK_TOOLPREFIX="xtensa-esp32-elf-" +# CONFIG_SDK_TOOLCHAIN_SUPPORTS_TIME_WIDE_64_BITS is not set +# end of SDK tool configuration + +# +# Build type +# +CONFIG_APP_BUILD_TYPE_APP_2NDBOOT=y +# CONFIG_APP_BUILD_TYPE_ELF_RAM is not set +CONFIG_APP_BUILD_GENERATE_BINARIES=y +CONFIG_APP_BUILD_BOOTLOADER=y +CONFIG_APP_BUILD_USE_FLASH_SECTIONS=y +# end of Build type + +# +# Application manager +# +CONFIG_APP_COMPILE_TIME_DATE=y +CONFIG_APP_EXCLUDE_PROJECT_VER_VAR=y +CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR=y +# CONFIG_APP_PROJECT_VER_FROM_CONFIG is not set +CONFIG_APP_RETRIEVE_LEN_ELF_SHA=16 +# end of Application manager + +# +# Bootloader config +# +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG is not set +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF is not set +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_NONE is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_NONE is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_ERROR is not set +CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y +# CONFIG_BOOTLOADER_LOG_LEVEL_INFO is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE is not set +CONFIG_BOOTLOADER_LOG_LEVEL=2 +# CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_8V is not set +CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_9V=y +# CONFIG_BOOTLOADER_FACTORY_RESET is not set +# CONFIG_BOOTLOADER_APP_TEST is not set +CONFIG_BOOTLOADER_WDT_ENABLE=y +# CONFIG_BOOTLOADER_WDT_DISABLE_IN_USER_CODE is not set +CONFIG_BOOTLOADER_WDT_TIME_MS=9000 +# CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE is not set +# CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP is not set +CONFIG_BOOTLOADER_RESERVE_RTC_SIZE=0 +# CONFIG_BOOTLOADER_CUSTOM_RESERVE_RTC is not set +# end of Bootloader config + +# +# Security features +# +# CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT is not set +# CONFIG_SECURE_BOOT is not set +# CONFIG_SECURE_FLASH_ENC_ENABLED is not set +# end of Security features + +# +# Serial flasher config +# +CONFIG_ESPTOOLPY_BAUD_OTHER_VAL=115200 +CONFIG_ESPTOOLPY_WITH_STUB=y +# CONFIG_ESPTOOLPY_FLASHMODE_QIO is not set +# CONFIG_ESPTOOLPY_FLASHMODE_QOUT is not set +CONFIG_ESPTOOLPY_FLASHMODE_DIO=y +# CONFIG_ESPTOOLPY_FLASHMODE_DOUT is not set +CONFIG_ESPTOOLPY_FLASHMODE="dio" +# CONFIG_ESPTOOLPY_FLASHFREQ_80M is not set +CONFIG_ESPTOOLPY_FLASHFREQ_40M=y +# CONFIG_ESPTOOLPY_FLASHFREQ_26M is not set +# CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set +CONFIG_ESPTOOLPY_FLASHFREQ="40m" +# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE="4MB" +CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y +CONFIG_ESPTOOLPY_BEFORE_RESET=y +# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set +CONFIG_ESPTOOLPY_BEFORE="default_reset" +CONFIG_ESPTOOLPY_AFTER_RESET=y +# CONFIG_ESPTOOLPY_AFTER_NORESET is not set +CONFIG_ESPTOOLPY_AFTER="hard_reset" +# CONFIG_ESPTOOLPY_MONITOR_BAUD_9600B is not set +# CONFIG_ESPTOOLPY_MONITOR_BAUD_57600B is not set +CONFIG_ESPTOOLPY_MONITOR_BAUD_115200B=y +# CONFIG_ESPTOOLPY_MONITOR_BAUD_230400B is not set +# CONFIG_ESPTOOLPY_MONITOR_BAUD_921600B is not set +# CONFIG_ESPTOOLPY_MONITOR_BAUD_2MB is not set +# CONFIG_ESPTOOLPY_MONITOR_BAUD_OTHER is not set +CONFIG_ESPTOOLPY_MONITOR_BAUD_OTHER_VAL=115200 +CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 +# end of Serial flasher config + +# +# Partition Table +# +# CONFIG_PARTITION_TABLE_SINGLE_APP is not set +# CONFIG_PARTITION_TABLE_TWO_OTA is not set +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_OFFSET=0x8000 +CONFIG_PARTITION_TABLE_MD5=y +# end of Partition Table + +# +# Compiler options +# +# CONFIG_COMPILER_OPTIMIZATION_DEFAULT is not set +# CONFIG_COMPILER_OPTIMIZATION_SIZE is not set +CONFIG_COMPILER_OPTIMIZATION_PERF=y +# CONFIG_COMPILER_OPTIMIZATION_NONE is not set +# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE is not set +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y +# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set +# CONFIG_COMPILER_CXX_EXCEPTIONS is not set +# CONFIG_COMPILER_CXX_RTTI is not set +CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y +# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set +# CONFIG_COMPILER_STACK_CHECK_MODE_STRONG is not set +# CONFIG_COMPILER_STACK_CHECK_MODE_ALL is not set +# CONFIG_COMPILER_WARN_WRITE_STRINGS is not set +# CONFIG_COMPILER_DISABLE_GCC8_WARNINGS is not set +# end of Compiler options + +# +# Component config +# + +# +# Application Level Tracing +# +# CONFIG_APPTRACE_DEST_TRAX is not set +CONFIG_APPTRACE_DEST_NONE=y +CONFIG_APPTRACE_LOCK_ENABLE=y +# end of Application Level Tracing + +# +# Bluetooth +# +CONFIG_BT_ENABLED=y + +# +# Bluetooth controller +# +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +# CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY is not set +# CONFIG_BTDM_CTRL_MODE_BTDM is not set +CONFIG_BTDM_CTRL_BLE_MAX_CONN=4 +CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_EFF=0 +CONFIG_BTDM_CTRL_PCM_ROLE_EFF=0 +CONFIG_BTDM_CTRL_PCM_POLAR_EFF=0 +CONFIG_BTDM_CTRL_BLE_MAX_CONN_EFF=4 +CONFIG_BTDM_CTRL_BR_EDR_MAX_ACL_CONN_EFF=0 +CONFIG_BTDM_CTRL_BR_EDR_MAX_SYNC_CONN_EFF=0 +CONFIG_BTDM_CTRL_PINNED_TO_CORE_0=y +# CONFIG_BTDM_CTRL_PINNED_TO_CORE_1 is not set +CONFIG_BTDM_CTRL_PINNED_TO_CORE=0 +CONFIG_BTDM_CTRL_HCI_MODE_VHCI=y +# CONFIG_BTDM_CTRL_HCI_MODE_UART_H4 is not set + +# +# MODEM SLEEP Options +# +CONFIG_BTDM_MODEM_SLEEP=y +CONFIG_BTDM_MODEM_SLEEP_MODE_ORIG=y +# CONFIG_BTDM_MODEM_SLEEP_MODE_EVED is not set +CONFIG_BTDM_LPCLK_SEL_MAIN_XTAL=y +# end of MODEM SLEEP Options + +CONFIG_BTDM_BLE_DEFAULT_SCA_250PPM=y +CONFIG_BTDM_BLE_SLEEP_CLOCK_ACCURACY_INDEX_EFF=1 +CONFIG_BTDM_BLE_SCAN_DUPL=y +CONFIG_BTDM_SCAN_DUPL_TYPE_DEVICE=y +# CONFIG_BTDM_SCAN_DUPL_TYPE_DATA is not set +# CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE is not set +CONFIG_BTDM_SCAN_DUPL_TYPE=0 +CONFIG_BTDM_SCAN_DUPL_CACHE_SIZE=100 +# CONFIG_BTDM_BLE_MESH_SCAN_DUPL_EN is not set +CONFIG_BTDM_CTRL_FULL_SCAN_SUPPORTED=y +CONFIG_BTDM_BLE_ADV_REPORT_FLOW_CTRL_SUPP=y +CONFIG_BTDM_BLE_ADV_REPORT_FLOW_CTRL_NUM=100 +CONFIG_BTDM_BLE_ADV_REPORT_DISCARD_THRSHOLD=20 +# end of Bluetooth controller + +# CONFIG_BT_BLUEDROID_ENABLED is not set +CONFIG_BT_NIMBLE_ENABLED=y +# CONFIG_BT_CONTROLLER_ONLY is not set +CONFIG_BT_RESERVE_DRAM=0xdb5c + +# +# NimBLE Options +# +CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y +# CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set +CONFIG_BT_NIMBLE_MAX_CONNECTIONS=3 +CONFIG_BT_NIMBLE_MAX_BONDS=3 +CONFIG_BT_NIMBLE_MAX_CCCDS=8 +CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM=0 +CONFIG_BT_NIMBLE_PINNED_TO_CORE_0=y +# CONFIG_BT_NIMBLE_PINNED_TO_CORE_1 is not set +CONFIG_BT_NIMBLE_PINNED_TO_CORE=0 +CONFIG_BT_NIMBLE_TASK_STACK_SIZE=4096 +CONFIG_BT_NIMBLE_ROLE_CENTRAL=y +CONFIG_BT_NIMBLE_ROLE_PERIPHERAL=y +CONFIG_BT_NIMBLE_ROLE_BROADCASTER=y +CONFIG_BT_NIMBLE_ROLE_OBSERVER=y +CONFIG_BT_NIMBLE_NVS_PERSIST=y +CONFIG_BT_NIMBLE_SM_LEGACY=y +CONFIG_BT_NIMBLE_SM_SC=y +CONFIG_BT_NIMBLE_DEBUG=y +# CONFIG_BT_NIMBLE_SM_SC_DEBUG_KEYS is not set +CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME="nimble" +CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=31 +CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=256 +CONFIG_BT_NIMBLE_SVC_GAP_APPEARANCE=0 +CONFIG_BT_NIMBLE_ACL_BUF_COUNT=12 +CONFIG_BT_NIMBLE_ACL_BUF_SIZE=255 +CONFIG_BT_NIMBLE_HCI_EVT_BUF_SIZE=70 +CONFIG_BT_NIMBLE_HCI_EVT_HI_BUF_COUNT=30 +CONFIG_BT_NIMBLE_HCI_EVT_LO_BUF_COUNT=8 +CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT=12 +CONFIG_BT_NIMBLE_HS_FLOW_CTRL=y +CONFIG_BT_NIMBLE_HS_FLOW_CTRL_ITVL=1000 +CONFIG_BT_NIMBLE_HS_FLOW_CTRL_THRESH=2 +CONFIG_BT_NIMBLE_HS_FLOW_CTRL_TX_ON_DISCONNECT=y +CONFIG_BT_NIMBLE_RPA_TIMEOUT=900 +# CONFIG_BT_NIMBLE_MESH is not set +CONFIG_BT_NIMBLE_CRYPTO_STACK_MBEDTLS=y +# end of NimBLE Options +# end of Bluetooth + +# CONFIG_BLE_MESH is not set + +# +# CoAP Configuration +# +CONFIG_COAP_MBEDTLS_PSK=y +# CONFIG_COAP_MBEDTLS_PKI is not set +# CONFIG_COAP_MBEDTLS_DEBUG is not set +CONFIG_COAP_LOG_DEFAULT_LEVEL=0 +# end of CoAP Configuration + +# +# Driver configurations +# + +# +# ADC configuration +# +# CONFIG_ADC_FORCE_XPD_FSM is not set +CONFIG_ADC_DISABLE_DAC=y +# end of ADC configuration + +# +# SPI configuration +# +# CONFIG_SPI_MASTER_IN_IRAM is not set +CONFIG_SPI_MASTER_ISR_IN_IRAM=y +# CONFIG_SPI_SLAVE_IN_IRAM is not set +CONFIG_SPI_SLAVE_ISR_IN_IRAM=y +# end of SPI configuration + +# +# UART configuration +# +# CONFIG_UART_ISR_IN_IRAM is not set +# end of UART configuration + +# +# RTCIO configuration +# +# CONFIG_RTCIO_SUPPORT_RTC_GPIO_DESC is not set +# end of RTCIO configuration +# end of Driver configurations + +# +# eFuse Bit Manager +# +# CONFIG_EFUSE_CUSTOM_TABLE is not set +# CONFIG_EFUSE_VIRTUAL is not set +# CONFIG_EFUSE_CODE_SCHEME_COMPAT_NONE is not set +CONFIG_EFUSE_CODE_SCHEME_COMPAT_3_4=y +# CONFIG_EFUSE_CODE_SCHEME_COMPAT_REPEAT is not set +CONFIG_EFUSE_MAX_BLK_LEN=192 +# end of eFuse Bit Manager + +# +# ESP-TLS +# +CONFIG_ESP_TLS_USING_MBEDTLS=y +# CONFIG_ESP_TLS_USE_SECURE_ELEMENT is not set +# CONFIG_ESP_TLS_SERVER is not set +# CONFIG_ESP_TLS_PSK_VERIFICATION is not set +# end of ESP-TLS + +# +# ESP32-specific +# +CONFIG_ESP32_REV_MIN_0=y +# CONFIG_ESP32_REV_MIN_1 is not set +# CONFIG_ESP32_REV_MIN_2 is not set +# CONFIG_ESP32_REV_MIN_3 is not set +CONFIG_ESP32_REV_MIN=0 +CONFIG_ESP32_DPORT_WORKAROUND=y +# CONFIG_ESP32_DEFAULT_CPU_FREQ_80 is not set +CONFIG_ESP32_DEFAULT_CPU_FREQ_160=y +# CONFIG_ESP32_DEFAULT_CPU_FREQ_240 is not set +CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=160 +# CONFIG_ESP32_SPIRAM_SUPPORT is not set +# CONFIG_ESP32_TRAX is not set +CONFIG_ESP32_TRACEMEM_RESERVE_DRAM=0x0 +# CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_TWO is not set +CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_FOUR=y +CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES=4 +CONFIG_ESP32_ULP_COPROC_ENABLED=y +CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=512 +CONFIG_ESP32_DEBUG_OCDAWARE=y +CONFIG_ESP32_BROWNOUT_DET=y +CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_0=y +# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_1 is not set +# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_2 is not set +# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_7 is not set +CONFIG_ESP32_BROWNOUT_DET_LVL=0 +CONFIG_ESP32_REDUCE_PHY_TX_POWER=y +CONFIG_ESP32_TIME_SYSCALL_USE_RTC_FRC1=y +# CONFIG_ESP32_TIME_SYSCALL_USE_RTC is not set +# CONFIG_ESP32_TIME_SYSCALL_USE_FRC1 is not set +# CONFIG_ESP32_TIME_SYSCALL_USE_NONE is not set +CONFIG_ESP32_RTC_CLK_SRC_INT_RC=y +# CONFIG_ESP32_RTC_CLK_SRC_EXT_CRYS is not set +# CONFIG_ESP32_RTC_CLK_SRC_EXT_OSC is not set +# CONFIG_ESP32_RTC_CLK_SRC_INT_8MD256 is not set +CONFIG_ESP32_RTC_CLK_CAL_CYCLES=1024 +CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY=2000 +# CONFIG_ESP32_XTAL_FREQ_40 is not set +# CONFIG_ESP32_XTAL_FREQ_26 is not set +CONFIG_ESP32_XTAL_FREQ_AUTO=y +CONFIG_ESP32_XTAL_FREQ=0 +# CONFIG_ESP32_DISABLE_BASIC_ROM_CONSOLE is not set +# CONFIG_ESP32_COMPATIBLE_PRE_V2_1_BOOTLOADERS is not set +# CONFIG_ESP32_USE_FIXED_STATIC_RAM_SIZE is not set +CONFIG_ESP32_DPORT_DIS_INTERRUPT_LVL=5 +# end of ESP32-specific + +# +# Power Management +# +CONFIG_PM_ENABLE=y +# CONFIG_PM_DFS_INIT_AUTO is not set +# CONFIG_PM_PROFILING is not set +# CONFIG_PM_TRACE is not set +# end of Power Management + +# +# ADC-Calibration +# +CONFIG_ADC_CAL_EFUSE_TP_ENABLE=y +CONFIG_ADC_CAL_EFUSE_VREF_ENABLE=y +CONFIG_ADC_CAL_LUT_ENABLE=y +# end of ADC-Calibration + +# +# Common ESP-related +# +CONFIG_ESP_ERR_TO_NAME_LOOKUP=y +CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=3584 +CONFIG_ESP_IPC_TASK_STACK_SIZE=1024 +CONFIG_ESP_IPC_USES_CALLERS_PRIORITY=y +CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=2048 +CONFIG_ESP_CONSOLE_UART_DEFAULT=y +# CONFIG_ESP_CONSOLE_UART_CUSTOM is not set +# CONFIG_ESP_CONSOLE_UART_NONE is not set +CONFIG_ESP_CONSOLE_UART_NUM=0 +CONFIG_ESP_CONSOLE_UART_TX_GPIO=1 +CONFIG_ESP_CONSOLE_UART_RX_GPIO=3 +CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 +CONFIG_ESP_INT_WDT=y +CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 +CONFIG_ESP_INT_WDT_CHECK_CPU1=y +CONFIG_ESP_TASK_WDT=y +# CONFIG_ESP_TASK_WDT_PANIC is not set +CONFIG_ESP_TASK_WDT_TIMEOUT_S=5 +# CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0 is not set +# CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1 is not set +# CONFIG_ESP_PANIC_HANDLER_IRAM is not set +CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_STA=y +CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_AP=y +CONFIG_ESP_MAC_ADDR_UNIVERSE_BT=y +CONFIG_ESP_MAC_ADDR_UNIVERSE_BT_OFFSET=2 +CONFIG_ESP_MAC_ADDR_UNIVERSE_ETH=y +# end of Common ESP-related + +# +# Ethernet +# +CONFIG_ETH_ENABLED=y +CONFIG_ETH_USE_ESP32_EMAC=y +CONFIG_ETH_PHY_INTERFACE_RMII=y +# CONFIG_ETH_PHY_INTERFACE_MII is not set +CONFIG_ETH_RMII_CLK_INPUT=y +# CONFIG_ETH_RMII_CLK_OUTPUT is not set +CONFIG_ETH_RMII_CLK_IN_GPIO=0 +CONFIG_ETH_DMA_BUFFER_SIZE=512 +CONFIG_ETH_DMA_RX_BUFFER_NUM=10 +CONFIG_ETH_DMA_TX_BUFFER_NUM=10 +CONFIG_ETH_USE_SPI_ETHERNET=y +CONFIG_ETH_SPI_ETHERNET_DM9051=y +# CONFIG_ETH_USE_OPENETH is not set +# end of Ethernet + +# +# Event Loop Library +# +# CONFIG_ESP_EVENT_LOOP_PROFILING is not set +CONFIG_ESP_EVENT_POST_FROM_ISR=y +CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y +# end of Event Loop Library + +# +# GDB Stub +# +# end of GDB Stub + +# +# ESP HTTP client +# +CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y +# CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set +# end of ESP HTTP client + +# +# HTTP Server +# +CONFIG_HTTPD_MAX_REQ_HDR_LEN=512 +CONFIG_HTTPD_MAX_URI_LEN=512 +CONFIG_HTTPD_ERR_RESP_NO_DELAY=y +CONFIG_HTTPD_PURGE_BUF_LEN=32 +# CONFIG_HTTPD_LOG_PURGE_DATA is not set +# CONFIG_HTTPD_WS_SUPPORT is not set +# end of HTTP Server + +# +# ESP HTTPS OTA +# +# CONFIG_OTA_ALLOW_HTTP is not set +# end of ESP HTTPS OTA + +# +# ESP HTTPS server +# +# CONFIG_ESP_HTTPS_SERVER_ENABLE is not set +# end of ESP HTTPS server + +# +# ESP NETIF Adapter +# +CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL=120 +CONFIG_ESP_NETIF_TCPIP_LWIP=y +# CONFIG_ESP_NETIF_LOOPBACK is not set +CONFIG_ESP_NETIF_TCPIP_ADAPTER_COMPATIBLE_LAYER=y +# end of ESP NETIF Adapter + +# +# ESP System Settings +# +# CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT is not set +CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y +# CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT is not set +# CONFIG_ESP_SYSTEM_PANIC_GDBSTUB is not set +# end of ESP System Settings + +# +# High resolution timer (esp_timer) +# +# CONFIG_ESP_TIMER_PROFILING is not set +CONFIG_ESP_TIMER_TASK_STACK_SIZE=3584 +# CONFIG_ESP_TIMER_IMPL_FRC2 is not set +CONFIG_ESP_TIMER_IMPL_TG0_LAC=y +# end of High resolution timer (esp_timer) + +# +# Wi-Fi +# +# CONFIG_ESP32_WIFI_SW_COEXIST_ENABLE is not set +CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10 +CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +# CONFIG_ESP32_WIFI_STATIC_TX_BUFFER is not set +CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER=y +CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=1 +CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32 +# CONFIG_ESP32_WIFI_CSI_ENABLED is not set +CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP32_WIFI_TX_BA_WIN=6 +CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP32_WIFI_RX_BA_WIN=6 +CONFIG_ESP32_WIFI_NVS_ENABLED=y +# CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0 is not set +CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1=y +CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752 +CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32 +# CONFIG_ESP32_WIFI_DEBUG_LOG_ENABLE is not set +# CONFIG_ESP32_WIFI_IRAM_OPT is not set +# CONFIG_ESP32_WIFI_RX_IRAM_OPT is not set +# CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE is not set +# end of Wi-Fi + +# +# PHY +# +CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE=y +# CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION is not set +CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20 +CONFIG_ESP32_PHY_MAX_TX_POWER=20 +# end of PHY + +# +# Core dump +# +# CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH is not set +# CONFIG_ESP32_ENABLE_COREDUMP_TO_UART is not set +CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y +# end of Core dump + +# +# FAT Filesystem support +# +# CONFIG_FATFS_CODEPAGE_DYNAMIC is not set +CONFIG_FATFS_CODEPAGE_437=y +# CONFIG_FATFS_CODEPAGE_720 is not set +# CONFIG_FATFS_CODEPAGE_737 is not set +# CONFIG_FATFS_CODEPAGE_771 is not set +# CONFIG_FATFS_CODEPAGE_775 is not set +# CONFIG_FATFS_CODEPAGE_850 is not set +# CONFIG_FATFS_CODEPAGE_852 is not set +# CONFIG_FATFS_CODEPAGE_855 is not set +# CONFIG_FATFS_CODEPAGE_857 is not set +# CONFIG_FATFS_CODEPAGE_860 is not set +# CONFIG_FATFS_CODEPAGE_861 is not set +# CONFIG_FATFS_CODEPAGE_862 is not set +# CONFIG_FATFS_CODEPAGE_863 is not set +# CONFIG_FATFS_CODEPAGE_864 is not set +# CONFIG_FATFS_CODEPAGE_865 is not set +# CONFIG_FATFS_CODEPAGE_866 is not set +# CONFIG_FATFS_CODEPAGE_869 is not set +# CONFIG_FATFS_CODEPAGE_932 is not set +# CONFIG_FATFS_CODEPAGE_936 is not set +# CONFIG_FATFS_CODEPAGE_949 is not set +# CONFIG_FATFS_CODEPAGE_950 is not set +CONFIG_FATFS_CODEPAGE=437 +CONFIG_FATFS_LFN_NONE=y +# CONFIG_FATFS_LFN_HEAP is not set +# CONFIG_FATFS_LFN_STACK is not set +CONFIG_FATFS_FS_LOCK=0 +CONFIG_FATFS_TIMEOUT_MS=10000 +CONFIG_FATFS_PER_FILE_CACHE=y +# end of FAT Filesystem support + +# +# Modbus configuration +# +CONFIG_FMB_COMM_MODE_RTU_EN=y +# CONFIG_FMB_COMM_MODE_ASCII_EN is not set +CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=150 +CONFIG_FMB_MASTER_DELAY_MS_CONVERT=200 +CONFIG_FMB_QUEUE_LENGTH=20 +CONFIG_FMB_SERIAL_TASK_STACK_SIZE=2048 +CONFIG_FMB_SERIAL_BUF_SIZE=256 +CONFIG_FMB_SERIAL_TASK_PRIO=10 +# CONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT is not set +CONFIG_FMB_CONTROLLER_NOTIFY_TIMEOUT=20 +CONFIG_FMB_CONTROLLER_NOTIFY_QUEUE_SIZE=20 +CONFIG_FMB_CONTROLLER_STACK_SIZE=4096 +CONFIG_FMB_EVENT_QUEUE_TIMEOUT=20 +CONFIG_FMB_TIMER_PORT_ENABLED=y +CONFIG_FMB_TIMER_GROUP=0 +CONFIG_FMB_TIMER_INDEX=0 +# CONFIG_FMB_TIMER_ISR_IN_IRAM is not set +# end of Modbus configuration + +# +# FreeRTOS +# +# CONFIG_FREERTOS_UNICORE is not set +CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF +CONFIG_FREERTOS_CORETIMER_0=y +# CONFIG_FREERTOS_CORETIMER_1 is not set +CONFIG_FREERTOS_HZ=100 +CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y +# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set +# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL is not set +CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y +# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set +CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y +CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2 +CONFIG_FREERTOS_ASSERT_FAIL_ABORT=y +# CONFIG_FREERTOS_ASSERT_FAIL_PRINT_CONTINUE is not set +# CONFIG_FREERTOS_ASSERT_DISABLE is not set +CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1536 +CONFIG_FREERTOS_ISR_STACKSIZE=1536 +# CONFIG_FREERTOS_LEGACY_HOOKS is not set +CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16 +CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y +CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP=y +CONFIG_FREERTOS_TIMER_TASK_PRIORITY=1 +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2048 +CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10 +CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0 +# CONFIG_FREERTOS_USE_TRACE_FACILITY is not set +# CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS is not set +# CONFIG_FREERTOS_USE_TICKLESS_IDLE is not set +CONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER=y +# CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set +CONFIG_FREERTOS_DEBUG_OCDAWARE=y +# CONFIG_FREERTOS_FPU_IN_ISR is not set +# end of FreeRTOS + +# +# Heap memory debugging +# +CONFIG_HEAP_POISONING_DISABLED=y +# CONFIG_HEAP_POISONING_LIGHT is not set +# CONFIG_HEAP_POISONING_COMPREHENSIVE is not set +CONFIG_HEAP_TRACING_OFF=y +# CONFIG_HEAP_TRACING_STANDALONE is not set +# CONFIG_HEAP_TRACING_TOHOST is not set +# CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS is not set +# end of Heap memory debugging + +# +# jsmn +# +# CONFIG_JSMN_PARENT_LINKS is not set +# CONFIG_JSMN_STRICT is not set +# end of jsmn + +# +# libsodium +# +# end of libsodium + +# +# Log output +# +# CONFIG_LOG_DEFAULT_LEVEL_NONE is not set +CONFIG_LOG_DEFAULT_LEVEL_ERROR=y +# CONFIG_LOG_DEFAULT_LEVEL_WARN is not set +# CONFIG_LOG_DEFAULT_LEVEL_INFO is not set +# CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set +# CONFIG_LOG_DEFAULT_LEVEL_VERBOSE is not set +CONFIG_LOG_DEFAULT_LEVEL=1 +CONFIG_LOG_COLORS=y +CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y +# CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set +# end of Log output + +# +# LWIP +# +CONFIG_LWIP_LOCAL_HOSTNAME="espressif" +CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y +# CONFIG_LWIP_L2_TO_L3_COPY is not set +# CONFIG_LWIP_IRAM_OPTIMIZATION is not set +CONFIG_LWIP_TIMERS_ONDEMAND=y +CONFIG_LWIP_MAX_SOCKETS=10 +# CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set +# CONFIG_LWIP_SO_LINGER is not set +CONFIG_LWIP_SO_REUSE=y +CONFIG_LWIP_SO_REUSE_RXTOALL=y +# CONFIG_LWIP_SO_RCVBUF is not set +# CONFIG_LWIP_NETBUF_RECVINFO is not set +CONFIG_LWIP_IP4_FRAG=y +CONFIG_LWIP_IP6_FRAG=y +# CONFIG_LWIP_IP4_REASSEMBLY is not set +# CONFIG_LWIP_IP6_REASSEMBLY is not set +# CONFIG_LWIP_IP_FORWARD is not set +# CONFIG_LWIP_STATS is not set +# CONFIG_LWIP_ETHARP_TRUST_IP_MAC is not set +CONFIG_LWIP_ESP_GRATUITOUS_ARP=y +CONFIG_LWIP_GARP_TMR_INTERVAL=60 +CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32 +CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y +# CONFIG_LWIP_DHCP_RESTORE_LAST_IP is not set + +# +# DHCP server +# +CONFIG_LWIP_DHCPS_LEASE_UNIT=60 +CONFIG_LWIP_DHCPS_MAX_STATION_NUM=8 +# end of DHCP server + +# CONFIG_LWIP_AUTOIP is not set +# CONFIG_LWIP_IPV6_AUTOCONFIG is not set +CONFIG_LWIP_NETIF_LOOPBACK=y +CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8 + +# +# TCP +# +CONFIG_LWIP_MAX_ACTIVE_TCP=16 +CONFIG_LWIP_MAX_LISTENING_TCP=16 +CONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y +CONFIG_LWIP_TCP_MAXRTX=12 +CONFIG_LWIP_TCP_SYNMAXRTX=6 +CONFIG_LWIP_TCP_MSS=1440 +CONFIG_LWIP_TCP_TMR_INTERVAL=250 +CONFIG_LWIP_TCP_MSL=60000 +CONFIG_LWIP_TCP_SND_BUF_DEFAULT=5744 +CONFIG_LWIP_TCP_WND_DEFAULT=5744 +CONFIG_LWIP_TCP_RECVMBOX_SIZE=6 +CONFIG_LWIP_TCP_QUEUE_OOSEQ=y +# CONFIG_LWIP_TCP_SACK_OUT is not set +# CONFIG_LWIP_TCP_KEEP_CONNECTION_WHEN_IP_CHANGES is not set +CONFIG_LWIP_TCP_OVERSIZE_MSS=y +# CONFIG_LWIP_TCP_OVERSIZE_QUARTER_MSS is not set +# CONFIG_LWIP_TCP_OVERSIZE_DISABLE is not set +CONFIG_LWIP_TCP_RTO_TIME=1500 +# end of TCP + +# +# UDP +# +CONFIG_LWIP_MAX_UDP_PCBS=16 +CONFIG_LWIP_UDP_RECVMBOX_SIZE=6 +# end of UDP + +CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=3072 +CONFIG_LWIP_TCPIP_TASK_AFFINITY_NO_AFFINITY=y +# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU0 is not set +# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU1 is not set +CONFIG_LWIP_TCPIP_TASK_AFFINITY=0x7FFFFFFF +CONFIG_LWIP_PPP_SUPPORT=y +CONFIG_LWIP_PPP_ENABLE_IPV6=y +CONFIG_LWIP_IPV6_MEMP_NUM_ND6_QUEUE=3 +CONFIG_LWIP_IPV6_ND6_NUM_NEIGHBORS=5 +# CONFIG_LWIP_PPP_NOTIFY_PHASE_SUPPORT is not set +CONFIG_LWIP_PPP_PAP_SUPPORT=y +CONFIG_LWIP_PPP_CHAP_SUPPORT=y +# CONFIG_LWIP_PPP_MSCHAP_SUPPORT is not set +# CONFIG_LWIP_PPP_MPPE_SUPPORT is not set +# CONFIG_LWIP_PPP_DEBUG_ON is not set + +# +# ICMP +# +# CONFIG_LWIP_MULTICAST_PING is not set +# CONFIG_LWIP_BROADCAST_PING is not set +# end of ICMP + +# +# LWIP RAW API +# +CONFIG_LWIP_MAX_RAW_PCBS=16 +# end of LWIP RAW API + +# +# SNTP +# +CONFIG_LWIP_DHCP_MAX_NTP_SERVERS=1 +CONFIG_LWIP_SNTP_UPDATE_DELAY=3600000 +# end of SNTP + +CONFIG_LWIP_ESP_LWIP_ASSERT=y +# end of LWIP + +# +# mbedTLS +# +CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y +# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set +# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set +CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y +CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384 +CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=4096 +# CONFIG_MBEDTLS_DYNAMIC_BUFFER is not set +# CONFIG_MBEDTLS_DEBUG is not set + +# +# Certificate Bundle +# +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN is not set +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_NONE is not set +# CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE is not set +# end of Certificate Bundle + +CONFIG_MBEDTLS_ECP_RESTARTABLE=y +CONFIG_MBEDTLS_CMAC_C=y +CONFIG_MBEDTLS_HARDWARE_AES=y +CONFIG_MBEDTLS_HARDWARE_MPI=y +CONFIG_MBEDTLS_HARDWARE_SHA=y +# CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN is not set +# CONFIG_MBEDTLS_ATCA_HW_ECDSA_VERIFY is not set +CONFIG_MBEDTLS_HAVE_TIME=y +# CONFIG_MBEDTLS_HAVE_TIME_DATE is not set +CONFIG_MBEDTLS_ECDSA_DETERMINISTIC=y +CONFIG_MBEDTLS_SHA512_C=y +CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y +# CONFIG_MBEDTLS_TLS_SERVER_ONLY is not set +# CONFIG_MBEDTLS_TLS_CLIENT_ONLY is not set +# CONFIG_MBEDTLS_TLS_DISABLED is not set +CONFIG_MBEDTLS_TLS_SERVER=y +CONFIG_MBEDTLS_TLS_CLIENT=y +CONFIG_MBEDTLS_TLS_ENABLED=y + +# +# TLS Key Exchange Methods +# +# CONFIG_MBEDTLS_PSK_MODES is not set +CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_DHE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=y +# end of TLS Key Exchange Methods + +CONFIG_MBEDTLS_SSL_RENEGOTIATION=y +# CONFIG_MBEDTLS_SSL_PROTO_SSL3 is not set +CONFIG_MBEDTLS_SSL_PROTO_TLS1=y +CONFIG_MBEDTLS_SSL_PROTO_TLS1_1=y +CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y +# CONFIG_MBEDTLS_SSL_PROTO_DTLS is not set +CONFIG_MBEDTLS_SSL_ALPN=y +CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS=y +CONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS=y + +# +# Symmetric Ciphers +# +CONFIG_MBEDTLS_AES_C=y +# CONFIG_MBEDTLS_CAMELLIA_C is not set +# CONFIG_MBEDTLS_DES_C is not set +CONFIG_MBEDTLS_RC4_DISABLED=y +# CONFIG_MBEDTLS_RC4_ENABLED_NO_DEFAULT is not set +# CONFIG_MBEDTLS_RC4_ENABLED is not set +# CONFIG_MBEDTLS_BLOWFISH_C is not set +# CONFIG_MBEDTLS_XTEA_C is not set +CONFIG_MBEDTLS_CCM_C=y +CONFIG_MBEDTLS_GCM_C=y +# end of Symmetric Ciphers + +# CONFIG_MBEDTLS_RIPEMD160_C is not set + +# +# Certificates +# +CONFIG_MBEDTLS_PEM_PARSE_C=y +CONFIG_MBEDTLS_PEM_WRITE_C=y +CONFIG_MBEDTLS_X509_CRL_PARSE_C=y +CONFIG_MBEDTLS_X509_CSR_PARSE_C=y +# end of Certificates + +CONFIG_MBEDTLS_ECP_C=y +CONFIG_MBEDTLS_ECDH_C=y +CONFIG_MBEDTLS_ECDSA_C=y +# CONFIG_MBEDTLS_ECJPAKE_C is not set +CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y +CONFIG_MBEDTLS_ECP_NIST_OPTIM=y +# CONFIG_MBEDTLS_POLY1305_C is not set +# CONFIG_MBEDTLS_CHACHA20_C is not set +# CONFIG_MBEDTLS_HKDF_C is not set +# CONFIG_MBEDTLS_THREADING_C is not set +# CONFIG_MBEDTLS_SECURITY_RISKS is not set +# end of mbedTLS + +# +# mDNS +# +CONFIG_MDNS_MAX_SERVICES=10 +CONFIG_MDNS_TASK_PRIORITY=1 +CONFIG_MDNS_TASK_STACK_SIZE=4096 +# CONFIG_MDNS_TASK_AFFINITY_NO_AFFINITY is not set +CONFIG_MDNS_TASK_AFFINITY_CPU0=y +# CONFIG_MDNS_TASK_AFFINITY_CPU1 is not set +CONFIG_MDNS_TASK_AFFINITY=0x0 +CONFIG_MDNS_SERVICE_ADD_TIMEOUT_MS=2000 +CONFIG_MDNS_TIMER_PERIOD_MS=100 +# end of mDNS + +# +# ESP-MQTT Configurations +# +CONFIG_MQTT_PROTOCOL_311=y +CONFIG_MQTT_TRANSPORT_SSL=y +CONFIG_MQTT_TRANSPORT_WEBSOCKET=y +CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=y +# CONFIG_MQTT_USE_CUSTOM_CONFIG is not set +# CONFIG_MQTT_TASK_CORE_SELECTION_ENABLED is not set +# CONFIG_MQTT_CUSTOM_OUTBOX is not set +# end of ESP-MQTT Configurations + +# +# Newlib +# +CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y +# CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF is not set +# CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR is not set +# CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF is not set +# CONFIG_NEWLIB_STDIN_LINE_ENDING_LF is not set +CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y +# CONFIG_NEWLIB_NANO_FORMAT is not set +# end of Newlib + +# +# NVS +# +# end of NVS + +# +# OpenSSL +# +# CONFIG_OPENSSL_DEBUG is not set +# CONFIG_OPENSSL_ASSERT_DO_NOTHING is not set +CONFIG_OPENSSL_ASSERT_EXIT=y +# end of OpenSSL + +# +# PThreads +# +CONFIG_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 +CONFIG_PTHREAD_STACK_MIN=768 +CONFIG_PTHREAD_DEFAULT_CORE_NO_AFFINITY=y +# CONFIG_PTHREAD_DEFAULT_CORE_0 is not set +# CONFIG_PTHREAD_DEFAULT_CORE_1 is not set +CONFIG_PTHREAD_TASK_CORE_DEFAULT=-1 +CONFIG_PTHREAD_TASK_NAME_DEFAULT="pthread" +# end of PThreads + +# +# SPI Flash driver +# +# CONFIG_SPI_FLASH_VERIFY_WRITE is not set +# CONFIG_SPI_FLASH_ENABLE_COUNTERS is not set +CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y +CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y +# CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS is not set +# CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED is not set +# CONFIG_SPI_FLASH_USE_LEGACY_IMPL is not set +# CONFIG_SPI_FLASH_SHARE_SPI1_BUS is not set +# CONFIG_SPI_FLASH_BYPASS_BLOCK_ERASE is not set +CONFIG_SPI_FLASH_YIELD_DURING_ERASE=y +CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS=20 +CONFIG_SPI_FLASH_ERASE_YIELD_TICKS=1 + +# +# Auto-detect flash chips +# +CONFIG_SPI_FLASH_SUPPORT_ISSI_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_MXIC_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_GD_CHIP=y +# end of Auto-detect flash chips +# end of SPI Flash driver + +# +# SPIFFS Configuration +# +CONFIG_SPIFFS_MAX_PARTITIONS=3 + +# +# SPIFFS Cache Configuration +# +CONFIG_SPIFFS_CACHE=y +CONFIG_SPIFFS_CACHE_WR=y +# CONFIG_SPIFFS_CACHE_STATS is not set +# end of SPIFFS Cache Configuration + +CONFIG_SPIFFS_PAGE_CHECK=y +CONFIG_SPIFFS_GC_MAX_RUNS=10 +# CONFIG_SPIFFS_GC_STATS is not set +CONFIG_SPIFFS_PAGE_SIZE=256 +CONFIG_SPIFFS_OBJ_NAME_LEN=32 +# CONFIG_SPIFFS_FOLLOW_SYMLINKS is not set +CONFIG_SPIFFS_USE_MAGIC=y +CONFIG_SPIFFS_USE_MAGIC_LENGTH=y +CONFIG_SPIFFS_META_LENGTH=4 +CONFIG_SPIFFS_USE_MTIME=y + +# +# Debug Configuration +# +# CONFIG_SPIFFS_DBG is not set +# CONFIG_SPIFFS_API_DBG is not set +# CONFIG_SPIFFS_GC_DBG is not set +# CONFIG_SPIFFS_CACHE_DBG is not set +# CONFIG_SPIFFS_CHECK_DBG is not set +# CONFIG_SPIFFS_TEST_VISUALISATION is not set +# end of Debug Configuration +# end of SPIFFS Configuration + +# +# TinyUSB +# + +# +# Descriptor configuration +# +CONFIG_USB_DESC_CUSTOM_VID=0x1234 +CONFIG_USB_DESC_CUSTOM_PID=0x5678 +# end of Descriptor configuration +# end of TinyUSB + +# +# Unity unit testing library +# +CONFIG_UNITY_ENABLE_FLOAT=y +CONFIG_UNITY_ENABLE_DOUBLE=y +# CONFIG_UNITY_ENABLE_COLOR is not set +CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y +# CONFIG_UNITY_ENABLE_FIXTURE is not set +# CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL is not set +# end of Unity unit testing library + +# +# Virtual file system +# +CONFIG_VFS_SUPPORT_IO=y +CONFIG_VFS_SUPPORT_DIR=y +CONFIG_VFS_SUPPORT_SELECT=y +CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT=y +CONFIG_VFS_SUPPORT_TERMIOS=y + +# +# Host File System I/O (Semihosting) +# +CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS=1 +CONFIG_VFS_SEMIHOSTFS_HOST_PATH_MAX_LEN=128 +# end of Host File System I/O (Semihosting) +# end of Virtual file system + +# +# Wear Levelling +# +# CONFIG_WL_SECTOR_SIZE_512 is not set +CONFIG_WL_SECTOR_SIZE_4096=y +CONFIG_WL_SECTOR_SIZE=4096 +# end of Wear Levelling + +# +# Wi-Fi Provisioning Manager +# +CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES=16 +CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30 +# end of Wi-Fi Provisioning Manager + +# +# Supplicant +# +CONFIG_WPA_MBEDTLS_CRYPTO=y +# CONFIG_WPA_DEBUG_PRINT is not set +# CONFIG_WPA_TESTING_OPTIONS is not set +# CONFIG_WPA_WPS_WARS is not set +# end of Supplicant +# end of Component config + +# +# Compatibility options +# +# CONFIG_LEGACY_INCLUDE_COMMON_HEADERS is not set +# end of Compatibility options + +# Deprecated options for backward compatibility +CONFIG_TOOLPREFIX="xtensa-esp32-elf-" +# CONFIG_LOG_BOOTLOADER_LEVEL_NONE is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set +CONFIG_LOG_BOOTLOADER_LEVEL_WARN=y +# CONFIG_LOG_BOOTLOADER_LEVEL_INFO is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set +CONFIG_LOG_BOOTLOADER_LEVEL=2 +# CONFIG_APP_ROLLBACK_ENABLE is not set +# CONFIG_FLASH_ENCRYPTION_ENABLED is not set +# CONFIG_FLASHMODE_QIO is not set +# CONFIG_FLASHMODE_QOUT is not set +CONFIG_FLASHMODE_DIO=y +# CONFIG_FLASHMODE_DOUT is not set +# CONFIG_MONITOR_BAUD_9600B is not set +# CONFIG_MONITOR_BAUD_57600B is not set +CONFIG_MONITOR_BAUD_115200B=y +# CONFIG_MONITOR_BAUD_230400B is not set +# CONFIG_MONITOR_BAUD_921600B is not set +# CONFIG_MONITOR_BAUD_2MB is not set +# CONFIG_MONITOR_BAUD_OTHER is not set +CONFIG_MONITOR_BAUD_OTHER_VAL=115200 +CONFIG_MONITOR_BAUD=115200 +# CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG is not set +# CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is not set +# CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED is not set +CONFIG_OPTIMIZATION_ASSERTIONS_SILENT=y +# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set +# CONFIG_CXX_EXCEPTIONS is not set +CONFIG_STACK_CHECK_NONE=y +# CONFIG_STACK_CHECK_NORM is not set +# CONFIG_STACK_CHECK_STRONG is not set +# CONFIG_STACK_CHECK_ALL is not set +# CONFIG_WARN_WRITE_STRINGS is not set +# CONFIG_DISABLE_GCC8_WARNINGS is not set +# CONFIG_ESP32_APPTRACE_DEST_TRAX is not set +CONFIG_ESP32_APPTRACE_DEST_NONE=y +CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y +CONFIG_BTDM_CONTROLLER_MODE_BLE_ONLY=y +# CONFIG_BTDM_CONTROLLER_MODE_BR_EDR_ONLY is not set +# CONFIG_BTDM_CONTROLLER_MODE_BTDM is not set +CONFIG_BTDM_CONTROLLER_BLE_MAX_CONN=4 +CONFIG_BTDM_CONTROLLER_BLE_MAX_CONN_EFF=4 +CONFIG_BTDM_CONTROLLER_BR_EDR_MAX_ACL_CONN_EFF=0 +CONFIG_BTDM_CONTROLLER_BR_EDR_MAX_SYNC_CONN_EFF=0 +CONFIG_BTDM_CONTROLLER_PINNED_TO_CORE=0 +CONFIG_BTDM_CONTROLLER_HCI_MODE_VHCI=y +# CONFIG_BTDM_CONTROLLER_HCI_MODE_UART_H4 is not set +CONFIG_BTDM_CONTROLLER_MODEM_SLEEP=y +CONFIG_BLE_SCAN_DUPLICATE=y +CONFIG_SCAN_DUPLICATE_BY_DEVICE_ADDR=y +# CONFIG_SCAN_DUPLICATE_BY_ADV_DATA is not set +# CONFIG_SCAN_DUPLICATE_BY_ADV_DATA_AND_DEVICE_ADDR is not set +CONFIG_SCAN_DUPLICATE_TYPE=0 +CONFIG_DUPLICATE_SCAN_CACHE_SIZE=100 +# CONFIG_BLE_MESH_SCAN_DUPLICATE_EN is not set +CONFIG_BTDM_CONTROLLER_FULL_SCAN_SUPPORTED=y +CONFIG_BLE_ADV_REPORT_FLOW_CONTROL_SUPPORTED=y +CONFIG_BLE_ADV_REPORT_FLOW_CONTROL_NUM=100 +CONFIG_BLE_ADV_REPORT_DISCARD_THRSHOLD=20 +# CONFIG_BLUEDROID_ENABLED is not set +CONFIG_NIMBLE_ENABLED=y +CONFIG_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y +# CONFIG_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set +CONFIG_NIMBLE_MAX_CONNECTIONS=3 +CONFIG_NIMBLE_MAX_BONDS=3 +CONFIG_NIMBLE_MAX_CCCDS=8 +CONFIG_NIMBLE_L2CAP_COC_MAX_NUM=0 +CONFIG_NIMBLE_PINNED_TO_CORE_0=y +# CONFIG_NIMBLE_PINNED_TO_CORE_1 is not set +CONFIG_NIMBLE_PINNED_TO_CORE=0 +CONFIG_NIMBLE_TASK_STACK_SIZE=4096 +CONFIG_NIMBLE_ROLE_CENTRAL=y +CONFIG_NIMBLE_ROLE_PERIPHERAL=y +CONFIG_NIMBLE_ROLE_BROADCASTER=y +CONFIG_NIMBLE_ROLE_OBSERVER=y +CONFIG_NIMBLE_NVS_PERSIST=y +CONFIG_NIMBLE_SM_LEGACY=y +CONFIG_NIMBLE_SM_SC=y +CONFIG_NIMBLE_DEBUG=y +# CONFIG_NIMBLE_SM_SC_DEBUG_KEYS is not set +CONFIG_NIMBLE_SVC_GAP_DEVICE_NAME="nimble" +CONFIG_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=31 +CONFIG_NIMBLE_ATT_PREFERRED_MTU=256 +CONFIG_NIMBLE_SVC_GAP_APPEARANCE=0 +CONFIG_NIMBLE_ACL_BUF_COUNT=12 +CONFIG_NIMBLE_ACL_BUF_SIZE=255 +CONFIG_NIMBLE_HCI_EVT_BUF_SIZE=70 +CONFIG_NIMBLE_HCI_EVT_HI_BUF_COUNT=30 +CONFIG_NIMBLE_HCI_EVT_LO_BUF_COUNT=8 +CONFIG_NIMBLE_MSYS1_BLOCK_COUNT=12 +CONFIG_NIMBLE_HS_FLOW_CTRL=y +CONFIG_NIMBLE_HS_FLOW_CTRL_ITVL=1000 +CONFIG_NIMBLE_HS_FLOW_CTRL_THRESH=2 +CONFIG_NIMBLE_HS_FLOW_CTRL_TX_ON_DISCONNECT=y +CONFIG_NIMBLE_RPA_TIMEOUT=900 +# CONFIG_NIMBLE_MESH is not set +CONFIG_NIMBLE_CRYPTO_STACK_MBEDTLS=y +CONFIG_ADC2_DISABLE_DAC=y +# CONFIG_SPIRAM_SUPPORT is not set +CONFIG_TRACEMEM_RESERVE_DRAM=0x0 +# CONFIG_TWO_UNIVERSAL_MAC_ADDRESS is not set +CONFIG_FOUR_UNIVERSAL_MAC_ADDRESS=y +CONFIG_NUMBER_OF_UNIVERSAL_MAC_ADDRESS=4 +CONFIG_ULP_COPROC_ENABLED=y +CONFIG_ULP_COPROC_RESERVE_MEM=512 +CONFIG_BROWNOUT_DET=y +CONFIG_BROWNOUT_DET_LVL_SEL_0=y +# CONFIG_BROWNOUT_DET_LVL_SEL_1 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_2 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_7 is not set +CONFIG_BROWNOUT_DET_LVL=0 +CONFIG_REDUCE_PHY_TX_POWER=y +CONFIG_ESP32_RTC_CLOCK_SOURCE_INTERNAL_RC=y +# CONFIG_ESP32_RTC_CLOCK_SOURCE_EXTERNAL_CRYSTAL is not set +# CONFIG_ESP32_RTC_CLOCK_SOURCE_EXTERNAL_OSC is not set +# CONFIG_ESP32_RTC_CLOCK_SOURCE_INTERNAL_8MD256 is not set +# CONFIG_DISABLE_BASIC_ROM_CONSOLE is not set +# CONFIG_COMPATIBLE_PRE_V2_1_BOOTLOADERS is not set +CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304 +CONFIG_MAIN_TASK_STACK_SIZE=3584 +CONFIG_IPC_TASK_STACK_SIZE=1024 +CONFIG_CONSOLE_UART_DEFAULT=y +# CONFIG_CONSOLE_UART_CUSTOM is not set +# CONFIG_CONSOLE_UART_NONE is not set +CONFIG_CONSOLE_UART_NUM=0 +CONFIG_CONSOLE_UART_TX_GPIO=1 +CONFIG_CONSOLE_UART_RX_GPIO=3 +CONFIG_CONSOLE_UART_BAUDRATE=115200 +CONFIG_INT_WDT=y +CONFIG_INT_WDT_TIMEOUT_MS=300 +CONFIG_INT_WDT_CHECK_CPU1=y +CONFIG_TASK_WDT=y +# CONFIG_TASK_WDT_PANIC is not set +CONFIG_TASK_WDT_TIMEOUT_S=5 +# CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 is not set +# CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1 is not set +# CONFIG_EVENT_LOOP_PROFILING is not set +CONFIG_POST_EVENTS_FROM_ISR=y +CONFIG_POST_EVENTS_FROM_IRAM_ISR=y +# CONFIG_ESP32S2_PANIC_PRINT_HALT is not set +CONFIG_ESP32S2_PANIC_PRINT_REBOOT=y +# CONFIG_ESP32S2_PANIC_SILENT_REBOOT is not set +# CONFIG_ESP32S2_PANIC_GDBSTUB is not set +CONFIG_TIMER_TASK_STACK_SIZE=3584 +# CONFIG_SW_COEXIST_ENABLE is not set +CONFIG_MB_MASTER_TIMEOUT_MS_RESPOND=150 +CONFIG_MB_MASTER_DELAY_MS_CONVERT=200 +CONFIG_MB_QUEUE_LENGTH=20 +CONFIG_MB_SERIAL_TASK_STACK_SIZE=2048 +CONFIG_MB_SERIAL_BUF_SIZE=256 +CONFIG_MB_SERIAL_TASK_PRIO=10 +# CONFIG_MB_CONTROLLER_SLAVE_ID_SUPPORT is not set +CONFIG_MB_CONTROLLER_NOTIFY_TIMEOUT=20 +CONFIG_MB_CONTROLLER_NOTIFY_QUEUE_SIZE=20 +CONFIG_MB_CONTROLLER_STACK_SIZE=4096 +CONFIG_MB_EVENT_QUEUE_TIMEOUT=20 +CONFIG_MB_TIMER_PORT_ENABLED=y +CONFIG_MB_TIMER_GROUP=0 +CONFIG_MB_TIMER_INDEX=0 +CONFIG_SUPPORT_STATIC_ALLOCATION=y +CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK=y +CONFIG_TIMER_TASK_PRIORITY=1 +CONFIG_TIMER_TASK_STACK_DEPTH=2048 +CONFIG_TIMER_QUEUE_LENGTH=10 +# CONFIG_L2_TO_L3_COPY is not set +# CONFIG_USE_ONLY_LWIP_SELECT is not set +CONFIG_ESP_GRATUITOUS_ARP=y +CONFIG_GARP_TMR_INTERVAL=60 +CONFIG_TCPIP_RECVMBOX_SIZE=32 +CONFIG_TCP_MAXRTX=12 +CONFIG_TCP_SYNMAXRTX=6 +CONFIG_TCP_MSS=1440 +CONFIG_TCP_MSL=60000 +CONFIG_TCP_SND_BUF_DEFAULT=5744 +CONFIG_TCP_WND_DEFAULT=5744 +CONFIG_TCP_RECVMBOX_SIZE=6 +CONFIG_TCP_QUEUE_OOSEQ=y +# CONFIG_ESP_TCP_KEEP_CONNECTION_WHEN_IP_CHANGES is not set +CONFIG_TCP_OVERSIZE_MSS=y +# CONFIG_TCP_OVERSIZE_QUARTER_MSS is not set +# CONFIG_TCP_OVERSIZE_DISABLE is not set +CONFIG_UDP_RECVMBOX_SIZE=6 +CONFIG_TCPIP_TASK_STACK_SIZE=3072 +CONFIG_TCPIP_TASK_AFFINITY_NO_AFFINITY=y +# CONFIG_TCPIP_TASK_AFFINITY_CPU0 is not set +# CONFIG_TCPIP_TASK_AFFINITY_CPU1 is not set +CONFIG_TCPIP_TASK_AFFINITY=0x7FFFFFFF +CONFIG_PPP_SUPPORT=y +# CONFIG_PPP_NOTIFY_PHASE_SUPPORT is not set +CONFIG_PPP_PAP_SUPPORT=y +CONFIG_PPP_CHAP_SUPPORT=y +# CONFIG_PPP_MSCHAP_SUPPORT is not set +# CONFIG_PPP_MPPE_SUPPORT is not set +# CONFIG_PPP_DEBUG_ON is not set +CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 +CONFIG_ESP32_PTHREAD_STACK_MIN=768 +CONFIG_ESP32_DEFAULT_PTHREAD_CORE_NO_AFFINITY=y +# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_0 is not set +# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_1 is not set +CONFIG_ESP32_PTHREAD_TASK_CORE_DEFAULT=-1 +CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT="pthread" +CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y +# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_FAILS is not set +# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED is not set +CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y +CONFIG_SUPPORT_TERMIOS=y +CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS=1 +CONFIG_SEMIHOSTFS_HOST_PATH_MAX_LEN=128 +# End of deprecated options