From 40ad8f1666b265dafc7844d765f45cfae4b6299f Mon Sep 17 00:00:00 2001 From: stijn Date: Thu, 18 Jun 2020 11:19:14 +0200 Subject: [PATCH 001/337] all: Rename "sys" module to "usys". This is consistent with the other 'micro' modules and allows implementing additional features in Python via e.g. micropython-lib's sys. Note this is a breaking change (not backwards compatible) for ports which do not enable weak links, as "import sys" must now be replaced with "import usys". --- docs/library/index.rst | 2 +- docs/library/{sys.rst => usys.rst} | 14 +++++++------- drivers/nrf24l01/nrf24l01test.py | 10 +++++----- extmod/webrepl/websocket_helper.py | 5 ++++- py/objmodule.c | 2 +- tests/basics/async_await2.py | 5 ++++- tests/basics/async_for2.py | 5 ++++- tests/basics/async_with2.py | 5 ++++- tests/basics/attrtuple1.py | 5 ++++- tests/basics/builtin_callable.py | 5 ++++- tests/basics/builtin_dir.py | 5 ++++- tests/basics/int_big1.py | 5 ++++- tests/basics/module2.py | 4 ++-- tests/basics/python34.py | 6 +++--- tests/basics/string_compare.py | 5 ++++- tests/basics/sys1.py | 5 ++++- tests/basics/sys_exit.py | 5 ++++- tests/basics/sys_getsizeof.py | 5 ++++- tests/extmod/uctypes_array_assign_native_le.py | 4 ++-- .../uctypes_array_assign_native_le_intbig.py | 4 ++-- tests/extmod/uctypes_native_le.py | 4 ++-- tests/extmod/uctypes_ptr_le.py | 4 ++-- tests/extmod/uctypes_ptr_native_le.py | 4 ++-- tests/extmod/vfs_fat_more.py | 6 +++--- tests/extmod/vfs_lfs_mount.py | 10 +++++----- tests/extmod/vfs_userfs.py | 6 +++--- tests/feature_check/byteorder.py | 5 ++++- tests/float/float2int_doubleprec_intbig.py | 4 ++-- tests/float/float2int_fp30_intbig.py | 4 ++-- tests/float/float2int_intbig.py | 4 ++-- tests/io/argv.py | 5 ++++- tests/io/builtin_print_file.py | 5 ++++- tests/io/file_stdio.py | 5 ++++- tests/io/resource_stream.py | 4 ++-- tests/micropython/emg_exc.py | 4 ++-- tests/micropython/heapalloc_traceback.py | 4 ++-- tests/micropython/import_mpy_invalid.py | 6 +++--- tests/micropython/import_mpy_native_x64.py | 8 ++++---- tests/micropython/opt_level_lineno.py | 2 +- tests/micropython/viper_misc_intbig.py | 4 ++-- tests/misc/print_exception.py | 4 ++-- tests/misc/sys_atexit.py | 6 +++--- tests/misc/sys_exc_info.py | 5 ++++- tests/run-natmodtests.py | 4 ++-- tests/run-tests-exp.py | 2 +- tests/thread/thread_stacksize1.py | 6 ++++-- 46 files changed, 142 insertions(+), 89 deletions(-) rename docs/library/{sys.rst => usys.rst} (93%) diff --git a/docs/library/index.rst b/docs/library/index.rst index 952e67d43c..43d9e87f3c 100644 --- a/docs/library/index.rst +++ b/docs/library/index.rst @@ -77,7 +77,6 @@ it will fallback to loading the built-in ``ujson`` module. cmath.rst gc.rst math.rst - sys.rst uarray.rst uasyncio.rst ubinascii.rst @@ -93,6 +92,7 @@ it will fallback to loading the built-in ``ujson`` module. usocket.rst ussl.rst ustruct.rst + usys.rst utime.rst uzlib.rst _thread.rst diff --git a/docs/library/sys.rst b/docs/library/usys.rst similarity index 93% rename from docs/library/sys.rst rename to docs/library/usys.rst index 24f9e353bb..9601643850 100644 --- a/docs/library/sys.rst +++ b/docs/library/usys.rst @@ -1,7 +1,7 @@ -:mod:`sys` -- system specific functions -======================================= +:mod:`usys` -- system specific functions +======================================== -.. module:: sys +.. module:: usys :synopsis: system specific functions |see_cpython_module| :mod:`python:sys`. @@ -28,10 +28,10 @@ Functions This function is a MicroPython extension intended to provide similar functionality to the :mod:`atexit` module in CPython. -.. function:: print_exception(exc, file=sys.stdout, /) +.. function:: print_exception(exc, file=usys.stdout, /) Print exception with a traceback to a file-like object *file* (or - `sys.stdout` by default). + `usys.stdout` by default). .. admonition:: Difference to CPython :class: attention @@ -84,7 +84,7 @@ Constants value directly, but instead count number of bits in it:: bits = 0 - v = sys.maxsize + v = usys.maxsize while v: bits += 1 v >>= 1 @@ -113,7 +113,7 @@ Constants is an identifier of a board, e.g. ``"pyboard"`` for the original MicroPython reference board. It thus can be used to distinguish one board from another. If you need to check whether your program runs on MicroPython (vs other - Python implementation), use `sys.implementation` instead. + Python implementation), use `usys.implementation` instead. .. data:: stderr diff --git a/drivers/nrf24l01/nrf24l01test.py b/drivers/nrf24l01/nrf24l01test.py index 14efbffd2a..56bdb6e26e 100644 --- a/drivers/nrf24l01/nrf24l01test.py +++ b/drivers/nrf24l01/nrf24l01test.py @@ -1,6 +1,6 @@ """Test for nrf24l01 module. Portable between MicroPython targets.""" -import sys +import usys import ustruct as struct import utime from machine import Pin, SPI @@ -14,14 +14,14 @@ _RX_POLL_DELAY = const(15) # master may be a slow device. Value tested with Pyboard, ESP32 and ESP8266. _SLAVE_SEND_DELAY = const(10) -if sys.platform == "pyboard": +if usys.platform == "pyboard": cfg = {"spi": 2, "miso": "Y7", "mosi": "Y8", "sck": "Y6", "csn": "Y5", "ce": "Y4"} -elif sys.platform == "esp8266": # Hardware SPI +elif usys.platform == "esp8266": # Hardware SPI cfg = {"spi": 1, "miso": 12, "mosi": 13, "sck": 14, "csn": 4, "ce": 5} -elif sys.platform == "esp32": # Software SPI +elif usys.platform == "esp32": # Software SPI cfg = {"spi": -1, "miso": 32, "mosi": 33, "sck": 25, "csn": 26, "ce": 27} else: - raise ValueError("Unsupported platform {}".format(sys.platform)) + raise ValueError("Unsupported platform {}".format(usys.platform)) # Addresses are in little-endian format. They correspond to big-endian # 0xf0f0f0f0e1, 0xf0f0f0f0d2 diff --git a/extmod/webrepl/websocket_helper.py b/extmod/webrepl/websocket_helper.py index 5ca80534eb..3260acc52f 100644 --- a/extmod/webrepl/websocket_helper.py +++ b/extmod/webrepl/websocket_helper.py @@ -1,4 +1,7 @@ -import sys +try: + import usys as sys +except ImportError: + import sys try: import ubinascii as binascii diff --git a/py/objmodule.c b/py/objmodule.c index 060e1bc1e6..a1f9d9d7f1 100644 --- a/py/objmodule.c +++ b/py/objmodule.c @@ -159,7 +159,7 @@ STATIC const mp_rom_map_elem_t mp_builtin_module_table[] = { #endif #endif #if MICROPY_PY_SYS - { MP_ROM_QSTR(MP_QSTR_sys), MP_ROM_PTR(&mp_module_sys) }, + { MP_ROM_QSTR(MP_QSTR_usys), MP_ROM_PTR(&mp_module_sys) }, #endif #if MICROPY_PY_GC && MICROPY_ENABLE_GC { MP_ROM_QSTR(MP_QSTR_gc), MP_ROM_PTR(&mp_module_gc) }, diff --git a/tests/basics/async_await2.py b/tests/basics/async_await2.py index 1e3164df93..56f77604ac 100644 --- a/tests/basics/async_await2.py +++ b/tests/basics/async_await2.py @@ -1,6 +1,9 @@ # test await expression -import sys +try: + import usys as sys +except ImportError: + import sys if sys.implementation.name == 'micropython': # uPy allows normal generators to be awaitables coroutine = lambda f: f diff --git a/tests/basics/async_for2.py b/tests/basics/async_for2.py index aad23a3e5a..4af3be4c6d 100644 --- a/tests/basics/async_for2.py +++ b/tests/basics/async_for2.py @@ -1,6 +1,9 @@ # test waiting within "async for" __anext__ function -import sys +try: + import usys as sys +except ImportError: + import sys if sys.implementation.name == 'micropython': # uPy allows normal generators to be awaitables coroutine = lambda f: f diff --git a/tests/basics/async_with2.py b/tests/basics/async_with2.py index 44421ae917..4dd1386240 100644 --- a/tests/basics/async_with2.py +++ b/tests/basics/async_with2.py @@ -1,6 +1,9 @@ # test waiting within async with enter/exit functions -import sys +try: + import usys as sys +except ImportError: + import sys if sys.implementation.name == 'micropython': # uPy allows normal generators to be awaitables coroutine = lambda f: f diff --git a/tests/basics/attrtuple1.py b/tests/basics/attrtuple1.py index 78a0fbed1b..249c030bb4 100644 --- a/tests/basics/attrtuple1.py +++ b/tests/basics/attrtuple1.py @@ -1,7 +1,10 @@ # test attrtuple # we can't test this type directly so we use sys.implementation object -import sys +try: + import usys as sys +except ImportError: + import sys t = sys.implementation # It can be just a normal tuple on small ports diff --git a/tests/basics/builtin_callable.py b/tests/basics/builtin_callable.py index 3ae49f004d..c0a9d0c473 100644 --- a/tests/basics/builtin_callable.py +++ b/tests/basics/builtin_callable.py @@ -7,7 +7,10 @@ print(callable([])) print(callable("dfsd")) # modules should not be callabe -import sys +try: + import usys as sys +except ImportError: + import sys print(callable(sys)) # builtins should be callable diff --git a/tests/basics/builtin_dir.py b/tests/basics/builtin_dir.py index 1eecbd044b..1f2b498d77 100644 --- a/tests/basics/builtin_dir.py +++ b/tests/basics/builtin_dir.py @@ -4,7 +4,10 @@ print('__name__' in dir()) # dir of module -import sys +try: + import usys as sys +except ImportError: + import sys print('version' in dir(sys)) # dir of type diff --git a/tests/basics/int_big1.py b/tests/basics/int_big1.py index 40d16c455b..108e3ee5c9 100644 --- a/tests/basics/int_big1.py +++ b/tests/basics/int_big1.py @@ -102,7 +102,10 @@ x = 4611686018427387904 # big x = -4611686018427387904 # big # sys.maxsize is a constant mpz, so test it's compatible with dynamic ones -import sys +try: + import usys as sys +except ImportError: + import sys print(sys.maxsize + 1 - 1 == sys.maxsize) # test extraction of big int value via mp_obj_get_int_maybe diff --git a/tests/basics/module2.py b/tests/basics/module2.py index a135601579..5923a27e08 100644 --- a/tests/basics/module2.py +++ b/tests/basics/module2.py @@ -1,6 +1,6 @@ # uPy behaviour only: builtin modules are read-only -import sys +import usys try: - sys.x = 1 + usys.x = 1 except AttributeError: print("AttributeError") diff --git a/tests/basics/python34.py b/tests/basics/python34.py index 4030db143c..0f6e4bafd0 100644 --- a/tests/basics/python34.py +++ b/tests/basics/python34.py @@ -33,9 +33,9 @@ test_syntax("del ()") # can't delete empty tuple (in 3.6 we can) # from basics/sys1.py # uPy prints version 3.4 -import sys -print(sys.version[:3]) -print(sys.version_info[0], sys.version_info[1]) +import usys +print(usys.version[:3]) +print(usys.version_info[0], usys.version_info[1]) # from basics/exception1.py # in 3.7 no comma is printed if there is only 1 arg (in 3.4-3.6 one is printed) diff --git a/tests/basics/string_compare.py b/tests/basics/string_compare.py index 6515809b36..f34879df25 100644 --- a/tests/basics/string_compare.py +++ b/tests/basics/string_compare.py @@ -51,7 +51,10 @@ print("1/" <= "1") # this tests an internal string that doesn't have a hash with a string # that does have a hash, but the lengths of the two strings are different -import sys +try: + import usys as sys +except ImportError: + import sys print(sys.version == 'a long string that has a hash') # this special string would have a hash of 0 but is incremented to 1 diff --git a/tests/basics/sys1.py b/tests/basics/sys1.py index 095824afaf..0947ea1964 100644 --- a/tests/basics/sys1.py +++ b/tests/basics/sys1.py @@ -1,6 +1,9 @@ # test sys module -import sys +try: + import usys as sys +except ImportError: + import sys print(sys.__name__) print(type(sys.path)) diff --git a/tests/basics/sys_exit.py b/tests/basics/sys_exit.py index b1f71549db..4d640ae60e 100644 --- a/tests/basics/sys_exit.py +++ b/tests/basics/sys_exit.py @@ -1,6 +1,9 @@ # test sys module's exit function -import sys +try: + import usys as sys +except ImportError: + import sys try: sys.exit diff --git a/tests/basics/sys_getsizeof.py b/tests/basics/sys_getsizeof.py index fe1b403e04..4dc919848c 100644 --- a/tests/basics/sys_getsizeof.py +++ b/tests/basics/sys_getsizeof.py @@ -1,6 +1,9 @@ # test sys.getsizeof() function -import sys +try: + import usys as sys +except ImportError: + import sys try: sys.getsizeof except AttributeError: diff --git a/tests/extmod/uctypes_array_assign_native_le.py b/tests/extmod/uctypes_array_assign_native_le.py index d4c27fc4b3..5bddfcdf2f 100644 --- a/tests/extmod/uctypes_array_assign_native_le.py +++ b/tests/extmod/uctypes_array_assign_native_le.py @@ -1,4 +1,4 @@ -import sys +import usys try: import uctypes @@ -6,7 +6,7 @@ except ImportError: print("SKIP") raise SystemExit -if sys.byteorder != "little": +if usys.byteorder != "little": print("SKIP") raise SystemExit diff --git a/tests/extmod/uctypes_array_assign_native_le_intbig.py b/tests/extmod/uctypes_array_assign_native_le_intbig.py index f33c63b4ef..42583b8afe 100644 --- a/tests/extmod/uctypes_array_assign_native_le_intbig.py +++ b/tests/extmod/uctypes_array_assign_native_le_intbig.py @@ -1,4 +1,4 @@ -import sys +import usys try: import uctypes @@ -6,7 +6,7 @@ except ImportError: print("SKIP") raise SystemExit -if sys.byteorder != "little": +if usys.byteorder != "little": print("SKIP") raise SystemExit diff --git a/tests/extmod/uctypes_native_le.py b/tests/extmod/uctypes_native_le.py index 7958e5c22a..9889b98e9d 100644 --- a/tests/extmod/uctypes_native_le.py +++ b/tests/extmod/uctypes_native_le.py @@ -1,7 +1,7 @@ # This test is exactly like uctypes_le.py, but uses native structure layout. # Codepaths for packed vs native structures are different. This test only works # on little-endian machine (no matter if 32 or 64 bit). -import sys +import usys try: import uctypes @@ -9,7 +9,7 @@ except ImportError: print("SKIP") raise SystemExit -if sys.byteorder != "little": +if usys.byteorder != "little": print("SKIP") raise SystemExit diff --git a/tests/extmod/uctypes_ptr_le.py b/tests/extmod/uctypes_ptr_le.py index 5d8094ee48..f475465ae8 100644 --- a/tests/extmod/uctypes_ptr_le.py +++ b/tests/extmod/uctypes_ptr_le.py @@ -1,4 +1,4 @@ -import sys +import usys try: import uctypes @@ -6,7 +6,7 @@ except ImportError: print("SKIP") raise SystemExit -if sys.byteorder != "little": +if usys.byteorder != "little": print("SKIP") raise SystemExit diff --git a/tests/extmod/uctypes_ptr_native_le.py b/tests/extmod/uctypes_ptr_native_le.py index 8ca4d2c55c..ca2b316c54 100644 --- a/tests/extmod/uctypes_ptr_native_le.py +++ b/tests/extmod/uctypes_ptr_native_le.py @@ -1,4 +1,4 @@ -import sys +import usys try: import uctypes @@ -6,7 +6,7 @@ except ImportError: print("SKIP") raise SystemExit -if sys.byteorder != "little": +if usys.byteorder != "little": print("SKIP") raise SystemExit diff --git a/tests/extmod/vfs_fat_more.py b/tests/extmod/vfs_fat_more.py index d8449829de..076802f6ad 100644 --- a/tests/extmod/vfs_fat_more.py +++ b/tests/extmod/vfs_fat_more.py @@ -111,10 +111,10 @@ print(uos.listdir()) print(uos.listdir("sys")) # test importing a file from a mounted FS -import sys +import usys -sys.path.clear() -sys.path.append("/sys") +usys.path.clear() +usys.path.append("/sys") with open("sys/test_module.py", "w") as f: f.write('print("test_module!")') import test_module diff --git a/tests/extmod/vfs_lfs_mount.py b/tests/extmod/vfs_lfs_mount.py index 9207f4a8c6..2c40b29897 100644 --- a/tests/extmod/vfs_lfs_mount.py +++ b/tests/extmod/vfs_lfs_mount.py @@ -68,17 +68,17 @@ def test(bdev, vfs_class): uos.umount("/lfs") # clear imported modules - sys.modules.clear() + usys.modules.clear() bdev = RAMBlockDevice(30) # initialise path -import sys +import usys -sys.path.clear() -sys.path.append("/lfs") -sys.path.append("") +usys.path.clear() +usys.path.append("/lfs") +usys.path.append("") # run tests test(bdev, uos.VfsLfs1) diff --git a/tests/extmod/vfs_userfs.py b/tests/extmod/vfs_userfs.py index 3cdfe82eea..570b833534 100644 --- a/tests/extmod/vfs_userfs.py +++ b/tests/extmod/vfs_userfs.py @@ -1,7 +1,7 @@ # test VFS functionality with a user-defined filesystem # also tests parts of uio.IOBase implementation -import sys +import usys try: import uio @@ -76,9 +76,9 @@ f = open("/userfs/data.txt") print(f.read()) # import files from the user filesystem -sys.path.append("/userfs") +usys.path.append("/userfs") import usermod1 # unmount and undo path addition uos.umount("/userfs") -sys.path.pop() +usys.path.pop() diff --git a/tests/feature_check/byteorder.py b/tests/feature_check/byteorder.py index c82a41a24b..509bd8b1b5 100644 --- a/tests/feature_check/byteorder.py +++ b/tests/feature_check/byteorder.py @@ -1,3 +1,6 @@ -import sys +try: + import usys as sys +except ImportError: + import sys print(sys.byteorder) diff --git a/tests/float/float2int_doubleprec_intbig.py b/tests/float/float2int_doubleprec_intbig.py index 84fa2d2536..418eddb607 100644 --- a/tests/float/float2int_doubleprec_intbig.py +++ b/tests/float/float2int_doubleprec_intbig.py @@ -2,10 +2,10 @@ try: import ustruct as struct + import usys as sys except: import struct - -import sys + import sys maxsize_bits = 0 maxsize = sys.maxsize diff --git a/tests/float/float2int_fp30_intbig.py b/tests/float/float2int_fp30_intbig.py index 75ff52f39d..fcbe2e309f 100644 --- a/tests/float/float2int_fp30_intbig.py +++ b/tests/float/float2int_fp30_intbig.py @@ -2,10 +2,10 @@ try: import ustruct as struct + import usys as sys except: import struct - -import sys + import sys maxsize_bits = 0 maxsize = sys.maxsize diff --git a/tests/float/float2int_intbig.py b/tests/float/float2int_intbig.py index 62aca39634..865aeea7b9 100644 --- a/tests/float/float2int_intbig.py +++ b/tests/float/float2int_intbig.py @@ -2,10 +2,10 @@ try: import ustruct as struct + import usys as sys except: import struct - -import sys + import sys maxsize_bits = 0 maxsize = sys.maxsize diff --git a/tests/io/argv.py b/tests/io/argv.py index 53254da119..834292504d 100644 --- a/tests/io/argv.py +++ b/tests/io/argv.py @@ -1,3 +1,6 @@ -import sys +try: + import usys as sys +except ImportError: + import sys print(sys.argv) diff --git a/tests/io/builtin_print_file.py b/tests/io/builtin_print_file.py index 822356a6cc..e5c20b64e4 100644 --- a/tests/io/builtin_print_file.py +++ b/tests/io/builtin_print_file.py @@ -1,6 +1,9 @@ # test builtin print function, using file= argument -import sys +try: + import usys as sys +except ImportError: + import sys try: sys.stdout diff --git a/tests/io/file_stdio.py b/tests/io/file_stdio.py index cbdb070163..6c08f35d78 100644 --- a/tests/io/file_stdio.py +++ b/tests/io/file_stdio.py @@ -1,4 +1,7 @@ -import sys +try: + import usys as sys +except ImportError: + import sys print(sys.stdin.fileno()) print(sys.stdout.fileno()) diff --git a/tests/io/resource_stream.py b/tests/io/resource_stream.py index 5656205b69..b589ff99bf 100644 --- a/tests/io/resource_stream.py +++ b/tests/io/resource_stream.py @@ -1,5 +1,5 @@ import uio -import sys +import usys try: uio.resource_stream @@ -11,5 +11,5 @@ buf = uio.resource_stream("data", "file2") print(buf.read()) # resource_stream(None, ...) look ups from current dir, hence sys.path[0] hack -buf = uio.resource_stream(None, sys.path[0] + "/data/file2") +buf = uio.resource_stream(None, usys.path[0] + "/data/file2") print(buf.read()) diff --git a/tests/micropython/emg_exc.py b/tests/micropython/emg_exc.py index bca4d2d9fb..b8df94b071 100644 --- a/tests/micropython/emg_exc.py +++ b/tests/micropython/emg_exc.py @@ -1,7 +1,7 @@ # test that emergency exceptions work import micropython -import sys +import usys try: import uio @@ -26,7 +26,7 @@ def f(): # print the exception buf = uio.StringIO() - sys.print_exception(exc, buf) + usys.print_exception(exc, buf) for l in buf.getvalue().split("\n"): if l.startswith(" File "): print(l.split('"')[2]) diff --git a/tests/micropython/heapalloc_traceback.py b/tests/micropython/heapalloc_traceback.py index eabd09388b..09a2ad2c14 100644 --- a/tests/micropython/heapalloc_traceback.py +++ b/tests/micropython/heapalloc_traceback.py @@ -1,7 +1,7 @@ # test that we can generate a traceback without allocating import micropython -import sys +import usys try: import uio @@ -33,7 +33,7 @@ test() # print the exception that was raised buf = uio.StringIO() -sys.print_exception(global_exc, buf) +usys.print_exception(global_exc, buf) for l in buf.getvalue().split("\n"): # uPy on pyboard prints as file, so remove filename. if l.startswith(" File "): diff --git a/tests/micropython/import_mpy_invalid.py b/tests/micropython/import_mpy_invalid.py index 00973fe3f9..02fd4b1254 100644 --- a/tests/micropython/import_mpy_invalid.py +++ b/tests/micropython/import_mpy_invalid.py @@ -1,7 +1,7 @@ # test importing of invalid .mpy files try: - import sys, uio, uos + import usys, uio, uos uio.IOBase uos.mount @@ -54,7 +54,7 @@ user_files = { # create and mount a user filesystem uos.mount(UserFS(user_files), "/userfs") -sys.path.append("/userfs") +usys.path.append("/userfs") # import .mpy files from the user filesystem for i in range(len(user_files)): @@ -66,4 +66,4 @@ for i in range(len(user_files)): # unmount and undo path addition uos.umount("/userfs") -sys.path.pop() +usys.path.pop() diff --git a/tests/micropython/import_mpy_native_x64.py b/tests/micropython/import_mpy_native_x64.py index d0de507d44..3e7b2058eb 100644 --- a/tests/micropython/import_mpy_native_x64.py +++ b/tests/micropython/import_mpy_native_x64.py @@ -1,7 +1,7 @@ # test importing of .mpy files with native code (x64 only) try: - import sys, uio, uos + import usys, uio, uos uio.IOBase uos.mount @@ -9,7 +9,7 @@ except (ImportError, AttributeError): print("SKIP") raise SystemExit -if not (sys.platform == "linux" and sys.maxsize > 2 ** 32): +if not (usys.platform == "linux" and usys.maxsize > 2 ** 32): print("SKIP") raise SystemExit @@ -101,7 +101,7 @@ user_files = { # create and mount a user filesystem uos.mount(UserFS(user_files), "/userfs") -sys.path.append("/userfs") +usys.path.append("/userfs") # import .mpy files from the user filesystem for i in range(len(user_files)): @@ -114,4 +114,4 @@ for i in range(len(user_files)): # unmount and undo path addition uos.umount("/userfs") -sys.path.pop() +usys.path.pop() diff --git a/tests/micropython/opt_level_lineno.py b/tests/micropython/opt_level_lineno.py index d8253e54b4..1cbf2fb1a8 100644 --- a/tests/micropython/opt_level_lineno.py +++ b/tests/micropython/opt_level_lineno.py @@ -3,4 +3,4 @@ import micropython as micropython # check that level 3 doesn't store line numbers # the expected output is that any line is printed as "line 1" micropython.opt_level(3) -exec("try:\n xyz\nexcept NameError as er:\n import sys\n sys.print_exception(er)") +exec("try:\n xyz\nexcept NameError as er:\n import usys\n usys.print_exception(er)") diff --git a/tests/micropython/viper_misc_intbig.py b/tests/micropython/viper_misc_intbig.py index 055c08d8e5..eda873ca63 100644 --- a/tests/micropython/viper_misc_intbig.py +++ b/tests/micropython/viper_misc_intbig.py @@ -6,6 +6,6 @@ def viper_uint() -> uint: return uint(-1) -import sys +import usys -print(viper_uint() == (sys.maxsize << 1 | 1)) +print(viper_uint() == (usys.maxsize << 1 | 1)) diff --git a/tests/misc/print_exception.py b/tests/misc/print_exception.py index f26c1fd5ac..9b3467b0a0 100644 --- a/tests/misc/print_exception.py +++ b/tests/misc/print_exception.py @@ -1,10 +1,10 @@ -import sys - try: try: import uio as io + import usys as sys except ImportError: import io + import sys except ImportError: print("SKIP") raise SystemExit diff --git a/tests/misc/sys_atexit.py b/tests/misc/sys_atexit.py index e9c5693f97..141b24cc9f 100644 --- a/tests/misc/sys_atexit.py +++ b/tests/misc/sys_atexit.py @@ -1,9 +1,9 @@ # test sys.atexit() function -import sys +import usys try: - sys.atexit + usys.atexit except AttributeError: print("SKIP") raise SystemExit @@ -15,7 +15,7 @@ def do_at_exit(): print("done at exit:", some_var) -sys.atexit(do_at_exit) +usys.atexit(do_at_exit) some_var = "ok" print("done before exit") diff --git a/tests/misc/sys_exc_info.py b/tests/misc/sys_exc_info.py index d7e8a2d943..3a8c4a6c8d 100644 --- a/tests/misc/sys_exc_info.py +++ b/tests/misc/sys_exc_info.py @@ -1,4 +1,7 @@ -import sys +try: + import usys as sys +except ImportError: + import sys try: sys.exc_info diff --git a/tests/run-natmodtests.py b/tests/run-natmodtests.py index f88ca0b788..8eb27169c4 100755 --- a/tests/run-natmodtests.py +++ b/tests/run-natmodtests.py @@ -30,7 +30,7 @@ TEST_MAPPINGS = { # Code to allow a target MicroPython to import an .mpy from RAM injected_import_hook_code = """\ -import sys, uos, uio +import usys, uos, uio class __File(uio.IOBase): def __init__(self): self.off = 0 @@ -54,7 +54,7 @@ class __FS: return __File() uos.mount(__FS(), '/__remote') uos.chdir('/__remote') -sys.modules['{}'] = __import__('__injected') +usys.modules['{}'] = __import__('__injected') """ diff --git a/tests/run-tests-exp.py b/tests/run-tests-exp.py index 34c6f650f8..ac32fe9869 100644 --- a/tests/run-tests-exp.py +++ b/tests/run-tests-exp.py @@ -5,7 +5,7 @@ # This script is intended to be run by the same interpreter executable # which is to be tested, so should use minimal language functionality. # -import sys +import usys as sys import uos as os diff --git a/tests/thread/thread_stacksize1.py b/tests/thread/thread_stacksize1.py index 5827237a17..5d25509b76 100644 --- a/tests/thread/thread_stacksize1.py +++ b/tests/thread/thread_stacksize1.py @@ -1,8 +1,10 @@ # test setting the thread stack size # # MIT license; Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd - -import sys +try: + import usys as sys +except ImportError: + import sys import _thread # different implementations have different minimum sizes From 38959ed8f11970d47f4e07b720a590e65e80dc35 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 4 Sep 2020 00:32:39 +1000 Subject: [PATCH 002/337] lib/libm: Reduce size of static two_over_pi array. Thanks to Jeff Epler for the idea. Signed-off-by: Damien George --- lib/libm/ef_rem_pio2.c | 4 ++-- lib/libm/fdlibm.h | 2 +- lib/libm/kf_rem_pio2.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/libm/ef_rem_pio2.c b/lib/libm/ef_rem_pio2.c index 8111ca197f..fcdfb18287 100644 --- a/lib/libm/ef_rem_pio2.c +++ b/lib/libm/ef_rem_pio2.c @@ -35,9 +35,9 @@ * Table of constants for 2/pi, 396 Hex digits (476 decimal) of 2/pi */ #ifdef __STDC__ -static const __int32_t two_over_pi[] = { +static const __uint8_t two_over_pi[] = { #else -static __int32_t two_over_pi[] = { +static __uint8_t two_over_pi[] = { #endif 0xA2, 0xF9, 0x83, 0x6E, 0x4E, 0x44, 0x15, 0x29, 0xFC, 0x27, 0x57, 0xD1, 0xF5, 0x34, 0xDD, 0xC0, 0xDB, 0x62, diff --git a/lib/libm/fdlibm.h b/lib/libm/fdlibm.h index ace3b2da22..22cd8f16b4 100644 --- a/lib/libm/fdlibm.h +++ b/lib/libm/fdlibm.h @@ -188,7 +188,7 @@ extern float __ieee754_scalbf __P((float,float)); extern float __kernel_sinf __P((float,float,int)); extern float __kernel_cosf __P((float,float)); extern float __kernel_tanf __P((float,float,int)); -extern int __kernel_rem_pio2f __P((float*,float*,int,int,int,const __int32_t*)); +extern int __kernel_rem_pio2f __P((float*,float*,int,int,int,const __uint8_t*)); /* A union which permits us to convert between a float and a 32 bit int. */ diff --git a/lib/libm/kf_rem_pio2.c b/lib/libm/kf_rem_pio2.c index 6bd4d287e6..43e4b4687f 100644 --- a/lib/libm/kf_rem_pio2.c +++ b/lib/libm/kf_rem_pio2.c @@ -62,10 +62,10 @@ two8 = 2.5600000000e+02f, /* 0x43800000 */ twon8 = 3.9062500000e-03f; /* 0x3b800000 */ #ifdef __STDC__ - int __kernel_rem_pio2f(float *x, float *y, int e0, int nx, int prec, const __int32_t *ipio2) + int __kernel_rem_pio2f(float *x, float *y, int e0, int nx, int prec, const __uint8_t *ipio2) #else int __kernel_rem_pio2f(x,y,e0,nx,prec,ipio2) - float x[], y[]; int e0,nx,prec; __int32_t ipio2[]; + float x[], y[]; int e0,nx,prec; __uint8_t ipio2[]; #endif { __int32_t jz,jx,jv,jp,jk,carry,n,iq[20],i,j,k,m,q0,ih; From 5e69926ea06cc035e831fcb657e756764682e0b5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 4 Sep 2020 01:04:08 +1000 Subject: [PATCH 003/337] stm32/mpconfigport.h: Enable MICROPY_PY_REVERSE_SPECIAL_METHODS. It's a useful core feature. Signed-off-by: Damien George --- ports/stm32/mpconfigport.h | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 6f38d90e8d..14a0589895 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -100,6 +100,7 @@ #define MICROPY_PY_BUILTINS_SLICE_INDICES (1) #define MICROPY_PY_BUILTINS_ROUND_INT (1) #define MICROPY_PY_ALL_SPECIAL_METHODS (1) +#define MICROPY_PY_REVERSE_SPECIAL_METHODS (1) #define MICROPY_PY_BUILTINS_COMPILE (MICROPY_ENABLE_COMPILER) #define MICROPY_PY_BUILTINS_EXECFILE (MICROPY_ENABLE_COMPILER) #define MICROPY_PY_BUILTINS_NOTIMPLEMENTED (1) From 3ff70792770e4591fec22fa6a1b50f492236fcde Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 4 Sep 2020 12:40:38 +1000 Subject: [PATCH 004/337] lib/utils/mpirq: Add mp_irq_init func, and clean up unused init method. mp_irq_init() is useful when the IRQ object is allocated by the caller. The mp_irq_methods_t.init method is not used anywhere so has been removed. Signed-off-by: Damien George --- lib/utils/mpirq.c | 6 +++++- lib/utils/mpirq.h | 14 +++++++------- ports/stm32/machine_uart.c | 1 - ports/zephyr/machine_pin.c | 7 +------ 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/utils/mpirq.c b/lib/utils/mpirq.c index 663be18224..02139f24dc 100644 --- a/lib/utils/mpirq.c +++ b/lib/utils/mpirq.c @@ -53,12 +53,16 @@ const mp_arg_t mp_irq_init_args[] = { mp_irq_obj_t *mp_irq_new(const mp_irq_methods_t *methods, mp_obj_t parent) { mp_irq_obj_t *self = m_new0(mp_irq_obj_t, 1); + mp_irq_init(self, methods, parent); + return self; +} + +void mp_irq_init(mp_irq_obj_t *self, const mp_irq_methods_t *methods, mp_obj_t parent) { self->base.type = &mp_irq_type; self->methods = (mp_irq_methods_t *)methods; self->parent = parent; self->handler = mp_const_none; self->ishard = false; - return self; } void mp_irq_handler(mp_irq_obj_t *self) { diff --git a/lib/utils/mpirq.h b/lib/utils/mpirq.h index 548185b531..186c9e1b0b 100644 --- a/lib/utils/mpirq.h +++ b/lib/utils/mpirq.h @@ -26,6 +26,8 @@ #ifndef MICROPY_INCLUDED_LIB_UTILS_MPIRQ_H #define MICROPY_INCLUDED_LIB_UTILS_MPIRQ_H +#include "py/obj.h" + /****************************************************************************** DEFINE CONSTANTS ******************************************************************************/ @@ -41,20 +43,17 @@ enum { DEFINE TYPES ******************************************************************************/ -typedef mp_obj_t (*mp_irq_init_t)(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); -typedef mp_uint_t (*mp_irq_uint_method_one_uint_para_t)(mp_obj_t self, mp_uint_t trigger); -typedef mp_uint_t (*mp_irq_int_method_one_para_t)(mp_obj_t self, mp_uint_t info_type); +typedef mp_uint_t (*mp_irq_trigger_fun_t)(mp_obj_t self, mp_uint_t trigger); +typedef mp_uint_t (*mp_irq_info_fun_t)(mp_obj_t self, mp_uint_t info_type); enum { MP_IRQ_INFO_FLAGS, MP_IRQ_INFO_TRIGGERS, - MP_IRQ_INFO_CNT }; typedef struct _mp_irq_methods_t { - mp_irq_init_t init; - mp_irq_uint_method_one_uint_para_t trigger; - mp_irq_int_method_one_para_t info; + mp_irq_trigger_fun_t trigger; + mp_irq_info_fun_t info; } mp_irq_methods_t; typedef struct _mp_irq_obj_t { @@ -77,6 +76,7 @@ extern const mp_obj_type_t mp_irq_type; ******************************************************************************/ mp_irq_obj_t *mp_irq_new(const mp_irq_methods_t *methods, mp_obj_t parent); +void mp_irq_init(mp_irq_obj_t *self, const mp_irq_methods_t *methods, mp_obj_t parent); void mp_irq_handler(mp_irq_obj_t *self); #endif // MICROPY_INCLUDED_LIB_UTILS_MPIRQ_H diff --git a/ports/stm32/machine_uart.c b/ports/stm32/machine_uart.c index 39a45a2a16..631f46e81f 100644 --- a/ports/stm32/machine_uart.c +++ b/ports/stm32/machine_uart.c @@ -139,7 +139,6 @@ STATIC mp_uint_t pyb_uart_irq_info(mp_obj_t self_in, mp_uint_t info_type) { } STATIC const mp_irq_methods_t pyb_uart_irq_methods = { - .init = pyb_uart_irq, .trigger = pyb_uart_irq_trigger, .info = pyb_uart_irq_info, }; diff --git a/ports/zephyr/machine_pin.c b/ports/zephyr/machine_pin.c index 79d759eb28..c9c6a8c893 100644 --- a/ports/zephyr/machine_pin.c +++ b/ports/zephyr/machine_pin.c @@ -213,11 +213,7 @@ STATIC mp_obj_t machine_pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_ } if (irq == NULL) { irq = m_new_obj(machine_pin_irq_obj_t); - irq->base.base.type = &mp_irq_type; - irq->base.methods = (mp_irq_methods_t *)&machine_pin_irq_methods; - irq->base.parent = MP_OBJ_FROM_PTR(self); - irq->base.handler = mp_const_none; - irq->base.ishard = false; + mp_irq_init(&irq->base, &machine_pin_irq_methods, MP_OBJ_FROM_PTR(self)); irq->next = MP_STATE_PORT(machine_pin_irq_list); gpio_init_callback(&irq->callback, gpio_callback_handler, BIT(self->pin)); int ret = gpio_add_callback(self->port, &irq->callback); @@ -322,7 +318,6 @@ STATIC mp_uint_t machine_pin_irq_info(mp_obj_t self_in, mp_uint_t info_type) { } STATIC const mp_irq_methods_t machine_pin_irq_methods = { - .init = machine_pin_irq, .trigger = machine_pin_irq_trigger, .info = machine_pin_irq_info, }; From 23109988c29447067b15f1943f72c0b486819c6c Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 14 Aug 2020 15:21:23 +1000 Subject: [PATCH 005/337] stm32/uart: Allow static IRQ handler registration. This will allow the HCI UART to use a non-heap mp_irq_obj_t, which avoids needing to make a root pointer for it. --- lib/utils/mpirq.h | 2 +- ports/stm32/machine_uart.c | 78 ++------------------------------------ ports/stm32/uart.c | 62 ++++++++++++++++++++++++++++++ ports/stm32/uart.h | 12 +++++- 4 files changed, 77 insertions(+), 77 deletions(-) diff --git a/lib/utils/mpirq.h b/lib/utils/mpirq.h index 186c9e1b0b..dd423c0101 100644 --- a/lib/utils/mpirq.h +++ b/lib/utils/mpirq.h @@ -26,7 +26,7 @@ #ifndef MICROPY_INCLUDED_LIB_UTILS_MPIRQ_H #define MICROPY_INCLUDED_LIB_UTILS_MPIRQ_H -#include "py/obj.h" +#include "py/runtime.h" /****************************************************************************** DEFINE CONSTANTS diff --git a/ports/stm32/machine_uart.c b/ports/stm32/machine_uart.c index 631f46e81f..2862f4c0e7 100644 --- a/ports/stm32/machine_uart.c +++ b/ports/stm32/machine_uart.c @@ -73,76 +73,6 @@ /// /// uart.any() # returns True if any characters waiting -typedef struct _pyb_uart_irq_map_t { - uint16_t irq_en; - uint16_t flag; -} pyb_uart_irq_map_t; - -STATIC const pyb_uart_irq_map_t mp_irq_map[] = { - { USART_CR1_IDLEIE, UART_FLAG_IDLE}, // RX idle - { USART_CR1_PEIE, UART_FLAG_PE}, // parity error - { USART_CR1_TXEIE, UART_FLAG_TXE}, // TX register empty - { USART_CR1_TCIE, UART_FLAG_TC}, // TX complete - { USART_CR1_RXNEIE, UART_FLAG_RXNE}, // RX register not empty - #if 0 - // For now only IRQs selected by CR1 are supported - #if defined(STM32F4) - { USART_CR2_LBDIE, UART_FLAG_LBD}, // LIN break detection - #else - { USART_CR2_LBDIE, UART_FLAG_LBDF}, // LIN break detection - #endif - { USART_CR3_CTSIE, UART_FLAG_CTS}, // CTS - #endif -}; - -// OR-ed IRQ flags which should not be touched by the user -STATIC const uint32_t mp_irq_reserved = UART_FLAG_RXNE; - -// OR-ed IRQ flags which are allowed to be used by the user -STATIC const uint32_t mp_irq_allowed = UART_FLAG_IDLE; - -STATIC mp_obj_t pyb_uart_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); - -STATIC void pyb_uart_irq_config(pyb_uart_obj_t *self, bool enable) { - if (self->mp_irq_trigger) { - for (size_t entry = 0; entry < MP_ARRAY_SIZE(mp_irq_map); ++entry) { - if (mp_irq_map[entry].flag & mp_irq_reserved) { - continue; - } - if (mp_irq_map[entry].flag & self->mp_irq_trigger) { - if (enable) { - self->uartx->CR1 |= mp_irq_map[entry].irq_en; - } else { - self->uartx->CR1 &= ~mp_irq_map[entry].irq_en; - } - } - } - } -} - -STATIC mp_uint_t pyb_uart_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { - pyb_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); - pyb_uart_irq_config(self, false); - self->mp_irq_trigger = new_trigger; - pyb_uart_irq_config(self, true); - return 0; -} - -STATIC mp_uint_t pyb_uart_irq_info(mp_obj_t self_in, mp_uint_t info_type) { - pyb_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); - if (info_type == MP_IRQ_INFO_FLAGS) { - return self->mp_irq_flags; - } else if (info_type == MP_IRQ_INFO_TRIGGERS) { - return self->mp_irq_trigger; - } - return 0; -} - -STATIC const mp_irq_methods_t pyb_uart_irq_methods = { - .trigger = pyb_uart_irq_trigger, - .info = pyb_uart_irq_info, -}; - STATIC void pyb_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { pyb_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); if (!self->is_enabled) { @@ -518,7 +448,7 @@ STATIC mp_obj_t pyb_uart_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t * if (self->mp_irq_obj == NULL) { self->mp_irq_trigger = 0; - self->mp_irq_obj = mp_irq_new(&pyb_uart_irq_methods, MP_OBJ_FROM_PTR(self)); + self->mp_irq_obj = mp_irq_new(&uart_irq_methods, MP_OBJ_FROM_PTR(self)); } if (n_args > 1 || kw_args->used != 0) { @@ -530,17 +460,17 @@ STATIC mp_obj_t pyb_uart_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t * // Check the trigger mp_uint_t trigger = args[MP_IRQ_ARG_INIT_trigger].u_int; - mp_uint_t not_supported = trigger & ~mp_irq_allowed; + mp_uint_t not_supported = trigger & ~MP_UART_ALLOWED_FLAGS; if (trigger != 0 && not_supported) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("trigger 0x%08x unsupported"), not_supported); } // Reconfigure user IRQs - pyb_uart_irq_config(self, false); + uart_irq_config(self, false); self->mp_irq_obj->handler = handler; self->mp_irq_obj->ishard = args[MP_IRQ_ARG_INIT_hard].u_bool; self->mp_irq_trigger = trigger; - pyb_uart_irq_config(self, true); + uart_irq_config(self, true); } return MP_OBJ_FROM_PTR(self->mp_irq_obj); diff --git a/ports/stm32/uart.c b/ports/stm32/uart.c index 8693813fa4..b14a18c6dd 100644 --- a/ports/stm32/uart.c +++ b/ports/stm32/uart.c @@ -93,6 +93,28 @@ extern void NORETURN __fatal_error(const char *msg); +typedef struct _pyb_uart_irq_map_t { + uint16_t irq_en; + uint16_t flag; +} pyb_uart_irq_map_t; + +STATIC const pyb_uart_irq_map_t mp_uart_irq_map[] = { + { USART_CR1_IDLEIE, UART_FLAG_IDLE}, // RX idle + { USART_CR1_PEIE, UART_FLAG_PE}, // parity error + { USART_CR1_TXEIE, UART_FLAG_TXE}, // TX register empty + { USART_CR1_TCIE, UART_FLAG_TC}, // TX complete + { USART_CR1_RXNEIE, UART_FLAG_RXNE}, // RX register not empty + #if 0 + // For now only IRQs selected by CR1 are supported + #if defined(STM32F4) + { USART_CR2_LBDIE, UART_FLAG_LBD}, // LIN break detection + #else + { USART_CR2_LBDIE, UART_FLAG_LBDF}, // LIN break detection + #endif + { USART_CR3_CTSIE, UART_FLAG_CTS}, // CTS + #endif +}; + void uart_init0(void) { #if defined(STM32H7) RCC_PeriphCLKInitTypeDef RCC_PeriphClkInit = {0}; @@ -444,6 +466,23 @@ bool uart_init(pyb_uart_obj_t *uart_obj, return true; } +void uart_irq_config(pyb_uart_obj_t *self, bool enable) { + if (self->mp_irq_trigger) { + for (size_t entry = 0; entry < MP_ARRAY_SIZE(mp_uart_irq_map); ++entry) { + if (mp_uart_irq_map[entry].flag & MP_UART_RESERVED_FLAGS) { + continue; + } + if (mp_uart_irq_map[entry].flag & self->mp_irq_trigger) { + if (enable) { + self->uartx->CR1 |= mp_uart_irq_map[entry].irq_en; + } else { + self->uartx->CR1 &= ~mp_uart_irq_map[entry].irq_en; + } + } + } + } +} + void uart_set_rxbuf(pyb_uart_obj_t *self, size_t len, void *buf) { self->read_buf_head = 0; self->read_buf_tail = 0; @@ -864,3 +903,26 @@ void uart_irq_handler(mp_uint_t uart_id) { mp_irq_handler(self->mp_irq_obj); } } + +STATIC mp_uint_t uart_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { + pyb_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + uart_irq_config(self, false); + self->mp_irq_trigger = new_trigger; + uart_irq_config(self, true); + return 0; +} + +STATIC mp_uint_t uart_irq_info(mp_obj_t self_in, mp_uint_t info_type) { + pyb_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (info_type == MP_IRQ_INFO_FLAGS) { + return self->mp_irq_flags; + } else if (info_type == MP_IRQ_INFO_TRIGGERS) { + return self->mp_irq_trigger; + } + return 0; +} + +const mp_irq_methods_t uart_irq_methods = { + .trigger = uart_irq_trigger, + .info = uart_irq_info, +}; diff --git a/ports/stm32/uart.h b/ports/stm32/uart.h index 62676fb91b..9a38db593d 100644 --- a/ports/stm32/uart.h +++ b/ports/stm32/uart.h @@ -26,7 +26,7 @@ #ifndef MICROPY_INCLUDED_STM32_UART_H #define MICROPY_INCLUDED_STM32_UART_H -struct _mp_irq_obj_t; +#include "lib/utils/mpirq.h" typedef enum { PYB_UART_NONE = 0, @@ -45,6 +45,12 @@ typedef enum { #define CHAR_WIDTH_8BIT (0) #define CHAR_WIDTH_9BIT (1) +// OR-ed IRQ flags which are allowed to be used by the user +#define MP_UART_ALLOWED_FLAGS UART_FLAG_IDLE + +// OR-ed IRQ flags which should not be touched by the user +#define MP_UART_RESERVED_FLAGS UART_FLAG_RXNE + typedef struct _pyb_uart_obj_t { mp_obj_base_t base; USART_TypeDef *uartx; @@ -62,16 +68,18 @@ typedef struct _pyb_uart_obj_t { byte *read_buf; // byte or uint16_t, depending on char size uint16_t mp_irq_trigger; // user IRQ trigger mask uint16_t mp_irq_flags; // user IRQ active IRQ flags - struct _mp_irq_obj_t *mp_irq_obj; // user IRQ object + mp_irq_obj_t *mp_irq_obj; // user IRQ object } pyb_uart_obj_t; extern const mp_obj_type_t pyb_uart_type; +extern const mp_irq_methods_t uart_irq_methods; void uart_init0(void); void uart_deinit_all(void); bool uart_exists(int uart_id); bool uart_init(pyb_uart_obj_t *uart_obj, uint32_t baudrate, uint32_t bits, uint32_t parity, uint32_t stop, uint32_t flow); +void uart_irq_config(pyb_uart_obj_t *self, bool enable); void uart_set_rxbuf(pyb_uart_obj_t *self, size_t len, void *buf); void uart_deinit(pyb_uart_obj_t *uart_obj); void uart_irq_handler(mp_uint_t uart_id); From 5ff265a3db998fd86a45d83ccca98fda153991c4 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 14 Aug 2020 16:35:25 +1000 Subject: [PATCH 006/337] stm32/modbluetooth_hci: Use a static mp_irq_obj_t for BT HCI UART IRQ. So that the IRQ handler does not need to be traced by the GC. --- ports/stm32/modbluetooth_hci.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ports/stm32/modbluetooth_hci.c b/ports/stm32/modbluetooth_hci.c index 4e016eae84..54c60adc01 100644 --- a/ports/stm32/modbluetooth_hci.c +++ b/ports/stm32/modbluetooth_hci.c @@ -135,6 +135,7 @@ int mp_bluetooth_hci_uart_readchar(void) { #include "uart.h" 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]; @@ -163,15 +164,14 @@ int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate) { } int mp_bluetooth_hci_uart_activate(void) { - // Interrupt on RX chunk received (idle) - // Trigger stack poll when this happens - mp_obj_t uart_irq_fn = mp_load_attr(MP_OBJ_FROM_PTR(&mp_bluetooth_hci_uart_obj), MP_QSTR_irq); - mp_obj_t uargs[] = { - MP_OBJ_FROM_PTR(&mp_uart_interrupt_obj), - MP_OBJ_NEW_SMALL_INT(UART_FLAG_IDLE), - mp_const_true, - }; - mp_call_function_n_kw(uart_irq_fn, 3, 0, uargs); + // Add IRQ handler for IDLE (i.e. packet finished). + uart_irq_config(&mp_bluetooth_hci_uart_obj, false); + mp_irq_init(&mp_bluetooth_hci_uart_irq_obj, &uart_irq_methods, MP_OBJ_FROM_PTR(&mp_bluetooth_hci_uart_obj)); + mp_bluetooth_hci_uart_obj.mp_irq_obj = &mp_bluetooth_hci_uart_irq_obj; + mp_bluetooth_hci_uart_obj.mp_irq_trigger = UART_FLAG_IDLE; + mp_bluetooth_hci_uart_irq_obj.handler = MP_OBJ_FROM_PTR(&mp_uart_interrupt_obj); + mp_bluetooth_hci_uart_irq_obj.ishard = true; + uart_irq_config(&mp_bluetooth_hci_uart_obj, true); mp_bluetooth_hci_controller_init(); mp_bluetooth_hci_controller_activate(); From e46aac24bacdafcb5323fbfc702a3bd597f66072 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 14 Aug 2020 16:04:31 +1000 Subject: [PATCH 007/337] extmod/modbluetooth: Rename logging macro to be just DEBUG_printf. And prefix the debug message with "btstack:" or "nimble:", depending on the context. Also use correct format specifier for %zu. --- extmod/btstack/modbluetooth_btstack.c | 146 ++++++++++++++------------ extmod/nimble/modbluetooth_nimble.c | 44 ++++---- 2 files changed, 100 insertions(+), 90 deletions(-) diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index 8f0c82974c..9aef7e6879 100644 --- a/extmod/btstack/modbluetooth_btstack.c +++ b/extmod/btstack/modbluetooth_btstack.c @@ -35,7 +35,7 @@ #include "lib/btstack/src/btstack.h" -#define DEBUG_EVENT_printf(...) // printf(__VA_ARGS__) +#define DEBUG_printf(...) // printf("btstack: " __VA_ARGS__) #ifndef MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME #define MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME "MPY BTSTACK" @@ -54,7 +54,7 @@ STATIC const uint16_t BTSTACK_GAP_DEVICE_NAME_HANDLE = 3; volatile int mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; STATIC int btstack_error_to_errno(int err) { - DEBUG_EVENT_printf(" --> btstack error: %d\n", err); + DEBUG_printf(" --> btstack error: %d\n", err); if (err == ERROR_CODE_SUCCESS) { return 0; } else if (err == BTSTACK_ACL_BUFFERS_FULL || err == BTSTACK_MEMORY_ALLOC_FAILED) { @@ -168,16 +168,16 @@ STATIC void btstack_remove_pending_operation(mp_btstack_pending_op_t *pending_op STATIC void btstack_notify_indicate_ready_handler(void *context) { MICROPY_PY_BLUETOOTH_ENTER mp_btstack_pending_op_t *pending_op = (mp_btstack_pending_op_t *)context; - DEBUG_EVENT_printf("btstack_notify_indicate_ready_handler op_type=%d conn_handle=%d value_handle=%d len=%lu\n", pending_op->op_type, pending_op->conn_handle, pending_op->value_handle, pending_op->len); + DEBUG_printf("btstack_notify_indicate_ready_handler op_type=%d conn_handle=%d value_handle=%d len=%zu\n", pending_op->op_type, pending_op->conn_handle, pending_op->value_handle, pending_op->len); if (pending_op->op_type == MP_BLUETOOTH_BTSTACK_PENDING_NOTIFY) { int err = att_server_notify(pending_op->conn_handle, pending_op->value_handle, pending_op->buf, pending_op->len); - DEBUG_EVENT_printf("btstack_notify_indicate_ready_handler: sending notification err=%d\n", err); + DEBUG_printf("btstack_notify_indicate_ready_handler: sending notification err=%d\n", err); assert(err == ERROR_CODE_SUCCESS); (void)err; } else { assert(pending_op->op_type == MP_BLUETOOTH_BTSTACK_PENDING_INDICATE); int err = att_server_indicate(pending_op->conn_handle, pending_op->value_handle, NULL, 0); - DEBUG_EVENT_printf("btstack_notify_indicate_ready_handler: sending indication err=%d\n", err); + DEBUG_printf("btstack_notify_indicate_ready_handler: sending indication err=%d\n", err); assert(err == ERROR_CODE_SUCCESS); (void)err; } @@ -188,7 +188,7 @@ STATIC void btstack_notify_indicate_ready_handler(void *context) { // Register a pending background operation -- copies the buffer, and makes it known to the GC. STATIC mp_btstack_pending_op_t *btstack_enqueue_pending_operation(uint16_t op_type, uint16_t conn_handle, uint16_t value_handle, const uint8_t *buf, size_t len) { - DEBUG_EVENT_printf("btstack_enqueue_pending_operation op_type=%d conn_handle=%d value_handle=%d len=%lu\n", op_type, conn_handle, value_handle, len); + DEBUG_printf("btstack_enqueue_pending_operation op_type=%d conn_handle=%d value_handle=%d len=%zu\n", op_type, conn_handle, value_handle, len); mp_btstack_pending_op_t *pending_op = m_new_obj_var(mp_btstack_pending_op_t, uint8_t, len); pending_op->op_type = op_type; pending_op->conn_handle = conn_handle; @@ -219,20 +219,20 @@ STATIC mp_btstack_pending_op_t *btstack_enqueue_pending_operation(uint16_t op_ty // know for sure that we're using the correct entry. STATIC mp_btstack_pending_op_t *btstack_finish_pending_operation(uint16_t op_type, uint16_t conn_handle, uint16_t value_handle, bool del) { MICROPY_PY_BLUETOOTH_ENTER - DEBUG_EVENT_printf("btstack_finish_pending_operation op_type=%d conn_handle=%d value_handle=%d\n", op_type, conn_handle, value_handle); + DEBUG_printf("btstack_finish_pending_operation op_type=%d conn_handle=%d value_handle=%d\n", op_type, conn_handle, value_handle); btstack_linked_list_iterator_t it; btstack_linked_list_iterator_init(&it, &MP_STATE_PORT(bluetooth_btstack_root_pointers)->pending_ops); while (btstack_linked_list_iterator_has_next(&it)) { mp_btstack_pending_op_t *pending_op = (mp_btstack_pending_op_t *)btstack_linked_list_iterator_next(&it); if (pending_op->op_type == op_type && pending_op->conn_handle == conn_handle && (value_handle == 0xffff || pending_op->value_handle == value_handle)) { - DEBUG_EVENT_printf("btstack_finish_pending_operation: found value_handle=%d len=%lu\n", pending_op->value_handle, pending_op->len); + DEBUG_printf("btstack_finish_pending_operation: found value_handle=%d len=%zu\n", pending_op->value_handle, pending_op->len); btstack_remove_pending_operation(pending_op, del); MICROPY_PY_BLUETOOTH_EXIT return del ? NULL : pending_op; } } - DEBUG_EVENT_printf("btstack_finish_pending_operation: not found\n"); + DEBUG_printf("btstack_finish_pending_operation: not found\n"); MICROPY_PY_BLUETOOTH_EXIT return NULL; } @@ -243,7 +243,7 @@ STATIC mp_btstack_pending_op_t *btstack_finish_pending_operation(uint16_t op_typ STATIC void btstack_packet_handler_att_server(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { (void)channel; (void)size; - DEBUG_EVENT_printf("btstack_packet_handler_att_server(packet_type=%u, packet=%p)\n", packet_type, packet); + DEBUG_printf("btstack_packet_handler_att_server(packet_type=%u, packet=%p)\n", packet_type, packet); if (packet_type != HCI_EVENT_PACKET) { return; } @@ -251,13 +251,13 @@ STATIC void btstack_packet_handler_att_server(uint8_t packet_type, uint16_t chan uint8_t event_type = hci_event_packet_get_type(packet); if (event_type == ATT_EVENT_CONNECTED) { - DEBUG_EVENT_printf(" --> att connected\n"); + DEBUG_printf(" --> att connected\n"); // The ATT_EVENT_*CONNECTED events are fired for both peripheral and central role, with no way to tell which. // So we use the HCI_EVENT_LE_META event directly in the main packet handler. } else if (event_type == ATT_EVENT_DISCONNECTED) { - DEBUG_EVENT_printf(" --> att disconnected\n"); + DEBUG_printf(" --> att disconnected\n"); } else if (event_type == ATT_EVENT_HANDLE_VALUE_INDICATION_COMPLETE) { - DEBUG_EVENT_printf(" --> att indication complete\n"); + DEBUG_printf(" --> att indication complete\n"); uint16_t conn_handle = att_event_handle_value_indication_complete_get_conn_handle(packet); uint16_t value_handle = att_event_handle_value_indication_complete_get_attribute_handle(packet); uint8_t status = att_event_handle_value_indication_complete_get_status(packet); @@ -265,12 +265,12 @@ STATIC void btstack_packet_handler_att_server(uint8_t packet_type, uint16_t chan } else if (event_type == HCI_EVENT_LE_META || event_type == HCI_EVENT_DISCONNECTION_COMPLETE) { // Ignore, duplicated by att_server.c. } else { - DEBUG_EVENT_printf(" --> hci att server event type: unknown (0x%02x)\n", event_type); + DEBUG_printf(" --> hci att server event type: unknown (0x%02x)\n", event_type); } } STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t irq) { - DEBUG_EVENT_printf("btstack_packet_handler(packet_type=%u, packet=%p)\n", packet_type, packet); + DEBUG_printf("btstack_packet_handler(packet_type=%u, packet=%p)\n", packet_type, packet); if (packet_type != HCI_EVENT_PACKET) { return; } @@ -278,7 +278,7 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t uint8_t event_type = hci_event_packet_get_type(packet); if (event_type == HCI_EVENT_LE_META) { - DEBUG_EVENT_printf(" --> hci le meta\n"); + 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); @@ -296,7 +296,7 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t } } else if (event_type == BTSTACK_EVENT_STATE) { uint8_t state = btstack_event_state_get_state(packet); - DEBUG_EVENT_printf(" --> btstack event state 0x%02x\n", state); + DEBUG_printf(" --> btstack event state 0x%02x\n", state); if (state == HCI_STATE_WORKING) { // Signal that initialisation has completed. mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_ACTIVE; @@ -305,21 +305,21 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; } } else if (event_type == HCI_EVENT_TRANSPORT_PACKET_SENT) { - DEBUG_EVENT_printf(" --> hci transport packet sent\n"); + DEBUG_printf(" --> hci transport packet sent\n"); } else if (event_type == HCI_EVENT_COMMAND_COMPLETE) { - DEBUG_EVENT_printf(" --> hci command complete\n"); + DEBUG_printf(" --> hci command complete\n"); } else if (event_type == HCI_EVENT_COMMAND_STATUS) { - DEBUG_EVENT_printf(" --> hci command status\n"); + DEBUG_printf(" --> hci command status\n"); } else if (event_type == HCI_EVENT_NUMBER_OF_COMPLETED_PACKETS) { - DEBUG_EVENT_printf(" --> hci number of completed packets\n"); + DEBUG_printf(" --> hci number of completed packets\n"); } else if (event_type == BTSTACK_EVENT_NR_CONNECTIONS_CHANGED) { - DEBUG_EVENT_printf(" --> btstack # conns changed\n"); + DEBUG_printf(" --> btstack # conns changed\n"); } else if (event_type == HCI_EVENT_VENDOR_SPECIFIC) { - DEBUG_EVENT_printf(" --> hci vendor specific\n"); + DEBUG_printf(" --> hci vendor specific\n"); } else if (event_type == GATT_EVENT_MTU) { - DEBUG_EVENT_printf(" --> hci MTU\n"); + DEBUG_printf(" --> hci MTU\n"); } else if (event_type == HCI_EVENT_DISCONNECTION_COMPLETE) { - DEBUG_EVENT_printf(" --> hci disconnect complete\n"); + DEBUG_printf(" --> hci disconnect complete\n"); uint16_t conn_handle = hci_event_disconnection_complete_get_connection_handle(packet); const hci_connection_t *conn = hci_connection_for_handle(conn_handle); uint16_t irq_event; @@ -334,7 +334,7 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t mp_bluetooth_gap_on_connected_disconnected(irq_event, conn_handle, 0xff, addr); #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE } else if (event_type == GAP_EVENT_ADVERTISING_REPORT) { - DEBUG_EVENT_printf(" --> gap advertising report\n"); + DEBUG_printf(" --> gap advertising report\n"); bd_addr_t address; gap_event_advertising_report_get_address(packet, address); uint8_t adv_event_type = gap_event_advertising_report_get_advertising_event_type(packet); @@ -346,7 +346,7 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t } else if (event_type == GATT_EVENT_QUERY_COMPLETE) { uint16_t conn_handle = gatt_event_query_complete_get_handle(packet); uint16_t status = gatt_event_query_complete_get_att_status(packet); - DEBUG_EVENT_printf(" --> gatt query complete irq=%d conn_handle=%d status=%d\n", irq, conn_handle, status); + DEBUG_printf(" --> gatt query complete irq=%d conn_handle=%d status=%d\n", irq, conn_handle, status); if (irq == MP_BLUETOOTH_IRQ_GATTC_READ_DONE || irq == MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE) { // TODO there is no value_handle available to pass here. // TODO try and get this implemented in btstack. @@ -361,28 +361,28 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t mp_bluetooth_gattc_on_discover_complete(irq, conn_handle, status); } } else if (event_type == GATT_EVENT_SERVICE_QUERY_RESULT) { - DEBUG_EVENT_printf(" --> gatt service query result\n"); + DEBUG_printf(" --> gatt service query result\n"); uint16_t conn_handle = gatt_event_service_query_result_get_handle(packet); gatt_client_service_t service; gatt_event_service_query_result_get_service(packet, &service); mp_obj_bluetooth_uuid_t service_uuid = create_mp_uuid(service.uuid16, service.uuid128); mp_bluetooth_gattc_on_primary_service_result(conn_handle, service.start_group_handle, service.end_group_handle, &service_uuid); } else if (event_type == GATT_EVENT_CHARACTERISTIC_QUERY_RESULT) { - DEBUG_EVENT_printf(" --> gatt characteristic query result\n"); + DEBUG_printf(" --> gatt characteristic query result\n"); uint16_t conn_handle = gatt_event_characteristic_query_result_get_handle(packet); gatt_client_characteristic_t characteristic; gatt_event_characteristic_query_result_get_characteristic(packet, &characteristic); mp_obj_bluetooth_uuid_t characteristic_uuid = create_mp_uuid(characteristic.uuid16, characteristic.uuid128); mp_bluetooth_gattc_on_characteristic_result(conn_handle, characteristic.start_handle, characteristic.value_handle, characteristic.properties, &characteristic_uuid); } else if (event_type == GATT_EVENT_CHARACTERISTIC_DESCRIPTOR_QUERY_RESULT) { - DEBUG_EVENT_printf(" --> gatt descriptor query result\n"); + DEBUG_printf(" --> gatt descriptor query result\n"); uint16_t conn_handle = gatt_event_all_characteristic_descriptors_query_result_get_handle(packet); gatt_client_characteristic_descriptor_t descriptor; gatt_event_all_characteristic_descriptors_query_result_get_characteristic_descriptor(packet, &descriptor); mp_obj_bluetooth_uuid_t descriptor_uuid = create_mp_uuid(descriptor.uuid16, descriptor.uuid128); mp_bluetooth_gattc_on_descriptor_result(conn_handle, descriptor.handle, &descriptor_uuid); } else if (event_type == GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT) { - DEBUG_EVENT_printf(" --> gatt characteristic value query result\n"); + DEBUG_printf(" --> gatt characteristic value query result\n"); uint16_t conn_handle = gatt_event_characteristic_value_query_result_get_handle(packet); 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); @@ -392,7 +392,7 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t mp_bluetooth_gattc_on_data_available_chunk(data, len); mp_bluetooth_gattc_on_data_available_end(atomic_state); } else if (event_type == GATT_EVENT_NOTIFICATION) { - DEBUG_EVENT_printf(" --> gatt notification\n"); + 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); @@ -402,7 +402,7 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t mp_bluetooth_gattc_on_data_available_chunk(data, len); mp_bluetooth_gattc_on_data_available_end(atomic_state); } else if (event_type == GATT_EVENT_INDICATION) { - DEBUG_EVENT_printf(" --> gatt indication\n"); + 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); @@ -413,17 +413,17 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t mp_bluetooth_gattc_on_data_available_end(atomic_state); } else if (event_type == GATT_EVENT_CAN_WRITE_WITHOUT_RESPONSE) { uint16_t conn_handle = gatt_event_can_write_without_response_get_handle(packet); - DEBUG_EVENT_printf(" --> gatt can write without response %d\n", conn_handle); + DEBUG_printf(" --> gatt can write without response %d\n", conn_handle); mp_btstack_pending_op_t *pending_op = btstack_finish_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_WRITE_NO_RESPONSE, conn_handle, 0xffff, false /* !del */); if (pending_op) { - DEBUG_EVENT_printf(" --> ready for value_handle=%d len=%lu\n", pending_op->value_handle, pending_op->len); + DEBUG_printf(" --> ready for value_handle=%d len=%zu\n", pending_op->value_handle, pending_op->len); gatt_client_write_value_of_characteristic_without_response(pending_op->conn_handle, pending_op->value_handle, pending_op->len, (uint8_t *)pending_op->buf); // Note: Can't "del" the pending_op from IRQ context. Leave it for the GC. } #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE } else { - DEBUG_EVENT_printf(" --> hci event type: unknown (0x%02x)\n", event_type); + DEBUG_printf(" --> hci event type: unknown (0x%02x)\n", event_type); } } @@ -489,7 +489,7 @@ STATIC void btstack_init_deinit_timeout_handler(btstack_timer_source_t *ds) { } int mp_bluetooth_init(void) { - DEBUG_EVENT_printf("mp_bluetooth_init\n"); + DEBUG_printf("mp_bluetooth_init\n"); if (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_ACTIVE) { return 0; @@ -538,6 +538,8 @@ int mp_bluetooth_init(void) { btstack_run_loop_set_timer_handler(&btstack_init_deinit_timeout, btstack_init_deinit_timeout_handler); btstack_run_loop_add_timer(&btstack_init_deinit_timeout); + DEBUG_printf("mp_bluetooth_init: waiting for stack startup\n"); + // Either the HCI event will set state to ACTIVE, or the timeout will set it to TIMEOUT. mp_bluetooth_btstack_port_start(); while (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_STARTING) { @@ -547,6 +549,8 @@ int mp_bluetooth_init(void) { // Check for timeout. if (mp_bluetooth_btstack_state != MP_BLUETOOTH_BTSTACK_STATE_ACTIVE) { + DEBUG_printf("mp_bluetooth_init: stack startup timed out\n"); + // Required to stop the polling loop. mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; // Attempt a shutdown (may not do anything). @@ -566,7 +570,7 @@ int mp_bluetooth_init(void) { } void mp_bluetooth_deinit(void) { - DEBUG_EVENT_printf("mp_bluetooth_deinit\n"); + DEBUG_printf("mp_bluetooth_deinit\n"); // Nothing to do if not initialised. if (!MP_STATE_PORT(bluetooth_btstack_root_pointers)) { @@ -595,6 +599,8 @@ void mp_bluetooth_deinit(void) { mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; MP_STATE_PORT(bluetooth_btstack_root_pointers) = NULL; + + DEBUG_printf("mp_bluetooth_deinit: complete\n"); } bool mp_bluetooth_is_active(void) { @@ -618,7 +624,7 @@ int mp_bluetooth_gap_set_device_name(const uint8_t *buf, size_t len) { } int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, const uint8_t *adv_data, size_t adv_data_len, const uint8_t *sr_data, size_t sr_data_len) { - DEBUG_EVENT_printf("mp_bluetooth_gap_advertise_start\n"); + DEBUG_printf("mp_bluetooth_gap_advertise_start\n"); uint16_t adv_int_min = interval_us / 625; uint16_t adv_int_max = interval_us / 625; uint8_t adv_type = connectable ? 0 : 2; @@ -654,14 +660,14 @@ int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, cons } void mp_bluetooth_gap_advertise_stop(void) { - DEBUG_EVENT_printf("mp_bluetooth_gap_advertise_stop\n"); + DEBUG_printf("mp_bluetooth_gap_advertise_stop\n"); gap_advertisements_enable(false); MP_STATE_PORT(bluetooth_btstack_root_pointers)->adv_data_alloc = 0; MP_STATE_PORT(bluetooth_btstack_root_pointers)->adv_data = NULL; } int mp_bluetooth_gatts_register_service_begin(bool append) { - DEBUG_EVENT_printf("mp_bluetooth_gatts_register_service_begin\n"); + DEBUG_printf("mp_bluetooth_gatts_register_service_begin\n"); if (!append) { // This will reset the DB. // Becase the DB is statically allocated, there's no problem with just re-initing it. @@ -682,10 +688,10 @@ int mp_bluetooth_gatts_register_service_begin(bool append) { STATIC uint16_t att_read_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) { (void)connection_handle; - DEBUG_EVENT_printf("btstack: att_read_callback (handle: %u, offset: %u, buffer: %p, size: %u)\n", att_handle, offset, buffer, buffer_size); + DEBUG_printf("btstack: att_read_callback (handle: %u, offset: %u, buffer: %p, size: %u)\n", att_handle, offset, buffer, buffer_size); mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, att_handle); if (!entry) { - DEBUG_EVENT_printf("btstack: att_read_callback handle not found\n"); + DEBUG_printf("btstack: att_read_callback handle not found\n"); return 0; // TODO: Find status code for not-found. } @@ -695,10 +701,10 @@ STATIC uint16_t att_read_callback(hci_con_handle_t connection_handle, uint16_t a STATIC int att_write_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) { (void)offset; (void)transaction_mode; - DEBUG_EVENT_printf("btstack: att_write_callback (handle: %u, mode: %u, offset: %u, buffer: %p, size: %u)\n", att_handle, transaction_mode, offset, buffer, buffer_size); + DEBUG_printf("btstack: att_write_callback (handle: %u, mode: %u, offset: %u, buffer: %p, size: %u)\n", att_handle, transaction_mode, offset, buffer, buffer_size); mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, att_handle); if (!entry) { - DEBUG_EVENT_printf("btstack: att_write_callback handle not found\n"); + DEBUG_printf("btstack: att_write_callback handle not found\n"); return 0; // TODO: Find status code for not-found. } @@ -720,7 +726,7 @@ STATIC inline uint16_t get_uuid16(const mp_obj_bluetooth_uuid_t *uuid) { } 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) { - DEBUG_EVENT_printf("mp_bluetooth_gatts_register_service\n"); + 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). @@ -764,7 +770,7 @@ int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, m return ret; } } - DEBUG_EVENT_printf("Registered char with handle %u\n", handles[handle_index]); + DEBUG_printf("mp_bluetooth_gatts_register_service: Registered char with handle %u\n", handles[handle_index]); ++handle_index; for (size_t j = 0; j < num_descriptors[i]; ++j) { @@ -782,7 +788,7 @@ int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, m return MP_EINVAL; } mp_bluetooth_gatts_db_create_entry(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, handles[handle_index], MP_BLUETOOTH_DEFAULT_ATTR_LEN); - DEBUG_EVENT_printf("Registered desc with handle %u\n", handles[handle_index]); + DEBUG_printf("mp_bluetooth_gatts_register_service: Registered desc with handle %u\n", handles[handle_index]); ++descriptor_index; ++handle_index; } @@ -792,23 +798,23 @@ int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, m } int mp_bluetooth_gatts_register_service_end(void) { - DEBUG_EVENT_printf("mp_bluetooth_gatts_register_service_end\n"); + DEBUG_printf("mp_bluetooth_gatts_register_service_end\n"); att_server_init(att_db_util_get_address(), &att_read_callback, &att_write_callback); return 0; } int mp_bluetooth_gatts_read(uint16_t value_handle, uint8_t **value, size_t *value_len) { - DEBUG_EVENT_printf("mp_bluetooth_gatts_read\n"); + DEBUG_printf("mp_bluetooth_gatts_read\n"); return mp_bluetooth_gatts_db_read(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, value_handle, value, value_len); } int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t value_len) { - DEBUG_EVENT_printf("mp_bluetooth_gatts_write\n"); + DEBUG_printf("mp_bluetooth_gatts_write\n"); return mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, value_handle, value, value_len); } int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle) { - DEBUG_EVENT_printf("mp_bluetooth_gatts_notify\n"); + DEBUG_printf("mp_bluetooth_gatts_notify\n"); // Note: btstack doesn't appear to support sending a notification without a value, so include the stored value. uint8_t *data = NULL; size_t len = 0; @@ -817,7 +823,7 @@ int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle) { } int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t value_len) { - DEBUG_EVENT_printf("mp_bluetooth_gatts_notify_send\n"); + DEBUG_printf("mp_bluetooth_gatts_notify_send\n"); // Attempt to send immediately. If it succeeds, btstack will copy the buffer. MICROPY_PY_BLUETOOTH_ENTER @@ -825,7 +831,7 @@ int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, MICROPY_PY_BLUETOOTH_EXIT if (err == BTSTACK_ACL_BUFFERS_FULL) { - DEBUG_EVENT_printf("mp_bluetooth_gatts_notify_send: ACL buffer full, scheduling callback\n"); + DEBUG_printf("mp_bluetooth_gatts_notify_send: ACL buffer full, scheduling callback\n"); // Schedule callback, making a copy of the buffer. mp_btstack_pending_op_t *pending_op = btstack_enqueue_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_NOTIFY, conn_handle, value_handle, value, value_len); @@ -843,7 +849,7 @@ int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, } int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle) { - DEBUG_EVENT_printf("mp_bluetooth_gatts_indicate\n"); + DEBUG_printf("mp_bluetooth_gatts_indicate\n"); uint8_t *data = NULL; size_t len = 0; @@ -858,7 +864,7 @@ int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle) { MICROPY_PY_BLUETOOTH_EXIT if (err == BTSTACK_ACL_BUFFERS_FULL) { - DEBUG_EVENT_printf("mp_bluetooth_gatts_indicate: ACL buffer full, scheduling callback\n"); + DEBUG_printf("mp_bluetooth_gatts_indicate: ACL buffer full, scheduling callback\n"); // Schedule callback, making a copy of the buffer. mp_btstack_pending_op_t *pending_op = btstack_enqueue_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_INDICATE, conn_handle, value_handle, data, len); @@ -876,12 +882,12 @@ int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle) { } int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append) { - DEBUG_EVENT_printf("mp_bluetooth_gatts_set_buffer\n"); + DEBUG_printf("mp_bluetooth_gatts_set_buffer\n"); return mp_bluetooth_gatts_db_resize(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, value_handle, len, append); } int mp_bluetooth_gap_disconnect(uint16_t conn_handle) { - DEBUG_EVENT_printf("mp_bluetooth_gap_disconnect\n"); + DEBUG_printf("mp_bluetooth_gap_disconnect\n"); gap_disconnect(conn_handle); return 0; } @@ -895,7 +901,7 @@ STATIC void scan_duration_timeout_handler(btstack_timer_source_t *ds) { } int mp_bluetooth_gap_scan_start(int32_t duration_ms, int32_t interval_us, int32_t window_us, bool active_scan) { - DEBUG_EVENT_printf("mp_bluetooth_gap_scan_start\n"); + DEBUG_printf("mp_bluetooth_gap_scan_start\n"); if (duration_ms > 0) { btstack_run_loop_set_timer(&scan_duration_timeout, duration_ms); @@ -910,7 +916,7 @@ int mp_bluetooth_gap_scan_start(int32_t duration_ms, int32_t interval_us, int32_ } int mp_bluetooth_gap_scan_stop(void) { - DEBUG_EVENT_printf("mp_bluetooth_gap_scan_stop\n"); + DEBUG_printf("mp_bluetooth_gap_scan_stop\n"); btstack_run_loop_remove_timer(&scan_duration_timeout); gap_stop_scan(); mp_bluetooth_gap_on_scan_complete(); @@ -918,7 +924,7 @@ int mp_bluetooth_gap_scan_stop(void) { } int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, int32_t duration_ms) { - DEBUG_EVENT_printf("mp_bluetooth_gap_peripheral_connect\n"); + DEBUG_printf("mp_bluetooth_gap_peripheral_connect\n"); uint16_t conn_scan_interval = 60000 / 625; uint16_t conn_scan_window = 30000 / 625; @@ -937,7 +943,7 @@ int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, } int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle, const mp_obj_bluetooth_uuid_t *uuid) { - DEBUG_EVENT_printf("mp_bluetooth_gattc_discover_primary_services\n"); + DEBUG_printf("mp_bluetooth_gattc_discover_primary_services\n"); uint8_t err; if (uuid) { if (uuid->type == MP_BLUETOOTH_UUID_TYPE_16) { @@ -947,7 +953,7 @@ int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle, const mp_ reverse_128(uuid->data, buffer); err = gatt_client_discover_primary_services_by_uuid128(&btstack_packet_handler_discover_services, conn_handle, buffer); } else { - DEBUG_EVENT_printf(" --> unknown UUID size\n"); + DEBUG_printf(" --> unknown UUID size\n"); return MP_EINVAL; } } else { @@ -957,7 +963,7 @@ int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle, const mp_ } int mp_bluetooth_gattc_discover_characteristics(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle, const mp_obj_bluetooth_uuid_t *uuid) { - DEBUG_EVENT_printf("mp_bluetooth_gattc_discover_characteristics\n"); + DEBUG_printf("mp_bluetooth_gattc_discover_characteristics\n"); gatt_client_service_t service = { // Only start/end handles needed for gatt_client_discover_characteristics_for_service. .start_group_handle = start_handle, @@ -974,7 +980,7 @@ int mp_bluetooth_gattc_discover_characteristics(uint16_t conn_handle, uint16_t s reverse_128(uuid->data, buffer); err = gatt_client_discover_characteristics_for_service_by_uuid128(&btstack_packet_handler_discover_characteristics, conn_handle, &service, buffer); } else { - DEBUG_EVENT_printf(" --> unknown UUID size\n"); + DEBUG_printf(" --> unknown UUID size\n"); return MP_EINVAL; } } else { @@ -984,7 +990,7 @@ int mp_bluetooth_gattc_discover_characteristics(uint16_t conn_handle, uint16_t s } int mp_bluetooth_gattc_discover_descriptors(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle) { - DEBUG_EVENT_printf("mp_bluetooth_gattc_discover_descriptors\n"); + DEBUG_printf("mp_bluetooth_gattc_discover_descriptors\n"); gatt_client_characteristic_t characteristic = { // Only start/end handles needed for gatt_client_discover_characteristic_descriptors. .start_handle = start_handle, @@ -998,12 +1004,12 @@ int mp_bluetooth_gattc_discover_descriptors(uint16_t conn_handle, uint16_t start } int mp_bluetooth_gattc_read(uint16_t conn_handle, uint16_t value_handle) { - DEBUG_EVENT_printf("mp_bluetooth_gattc_read\n"); + DEBUG_printf("mp_bluetooth_gattc_read\n"); return btstack_error_to_errno(gatt_client_read_value_of_characteristic_using_value_handle(&btstack_packet_handler_read, conn_handle, value_handle)); } int mp_bluetooth_gattc_write(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len, unsigned int mode) { - DEBUG_EVENT_printf("mp_bluetooth_gattc_write\n"); + DEBUG_printf("mp_bluetooth_gattc_write\n"); // We should be distinguishing between gatt_client_write_value_of_characteristic vs // gatt_client_write_characteristic_descriptor_using_descriptor_handle. @@ -1018,13 +1024,13 @@ int mp_bluetooth_gattc_write(uint16_t conn_handle, uint16_t value_handle, const // If possible, this will send immediately, copying the buffer directly to the ACL buffer. err = gatt_client_write_value_of_characteristic_without_response(conn_handle, value_handle, *value_len, (uint8_t *)value); if (err == GATT_CLIENT_BUSY) { - DEBUG_EVENT_printf("mp_bluetooth_gattc_write: client busy\n"); + DEBUG_printf("mp_bluetooth_gattc_write: client busy\n"); // Can't send right now, need to take a copy of the buffer and add it to the queue. pending_op = btstack_enqueue_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_WRITE_NO_RESPONSE, conn_handle, value_handle, value, *value_len); // Notify when this conn_handle can write. err = gatt_client_request_can_write_without_response_event(&btstack_packet_handler_generic, conn_handle); } else { - DEBUG_EVENT_printf("mp_bluetooth_gattc_write: other failure: %d\n", err); + DEBUG_printf("mp_bluetooth_gattc_write: other failure: %d\n", err); } } else if (mode == MP_BLUETOOTH_WRITE_MODE_WITH_RESPONSE) { // Pending operation copies the value buffer and keeps a GC reference diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index e51f2747ec..36bfa6cccf 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -45,7 +45,7 @@ #define MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME "MPY NIMBLE" #endif -#define DEBUG_EVENT_printf(...) // printf(__VA_ARGS__) +#define DEBUG_printf(...) // printf("nimble: " __VA_ARGS__) #define ERRNO_BLUETOOTH_NOT_ACTIVE MP_ENODEV @@ -172,10 +172,12 @@ STATIC void sync_cb(void) { } if (MP_BLUETOOTH_DEFAULT_ATTR_LEN > 20) { + DEBUG_printf("sync_cb: Setting MTU\n"); rc = ble_att_set_preferred_mtu(MP_BLUETOOTH_DEFAULT_ATTR_LEN + 3); assert(rc == 0); } + DEBUG_printf("sync_cb: Setting device name\n"); ble_svc_gap_device_name_set(MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME); mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE; @@ -188,12 +190,12 @@ STATIC void gatts_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) { switch (ctxt->op) { case BLE_GATT_REGISTER_OP_SVC: // Called when a service is successfully registered. - DEBUG_EVENT_printf("gatts_register_cb: svc uuid=%p handle=%d\n", &ctxt->svc.svc_def->uuid, ctxt->svc.handle); + DEBUG_printf("gatts_register_cb: svc uuid=%p handle=%d\n", &ctxt->svc.svc_def->uuid, ctxt->svc.handle); break; case BLE_GATT_REGISTER_OP_CHR: // Called when a characteristic is successfully registered. - DEBUG_EVENT_printf("gatts_register_cb: chr uuid=%p def_handle=%d val_handle=%d\n", &ctxt->chr.chr_def->uuid, ctxt->chr.def_handle, ctxt->chr.val_handle); + DEBUG_printf("gatts_register_cb: chr uuid=%p def_handle=%d val_handle=%d\n", &ctxt->chr.chr_def->uuid, ctxt->chr.def_handle, ctxt->chr.val_handle); // Note: We will get this event for the default GAP Service, meaning that we allocate storage for the // "device name" and "appearance" characteristics, even though we never see the reads for them. @@ -207,7 +209,7 @@ STATIC void gatts_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) { case BLE_GATT_REGISTER_OP_DSC: // Called when a descriptor is successfully registered. // Note: This is event is not called for the CCCD. - DEBUG_EVENT_printf("gatts_register_cb: dsc uuid=%p handle=%d\n", &ctxt->dsc.dsc_def->uuid, ctxt->dsc.handle); + DEBUG_printf("gatts_register_cb: dsc uuid=%p handle=%d\n", &ctxt->dsc.dsc_def->uuid, ctxt->dsc.handle); // See above, safe to alloc. mp_bluetooth_gatts_db_create_entry(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, ctxt->dsc.handle, MP_BLUETOOTH_DEFAULT_ATTR_LEN); @@ -217,13 +219,13 @@ STATIC void gatts_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) { break; default: - DEBUG_EVENT_printf("gatts_register_cb: unknown op %d\n", ctxt->op); + DEBUG_printf("gatts_register_cb: unknown op %d\n", ctxt->op); break; } } STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { - DEBUG_EVENT_printf("gap_event_cb: type=%d\n", event->type); + DEBUG_printf("gap_event_cb: type=%d\n", event->type); if (!mp_bluetooth_is_active()) { return 0; } @@ -250,7 +252,7 @@ STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { break; case BLE_GAP_EVENT_NOTIFY_TX: { - DEBUG_EVENT_printf("gap_event_cb: notify_tx: %d %d\n", event->notify_tx.indication, event->notify_tx.status); + DEBUG_printf("gap_event_cb: notify_tx: %d %d\n", event->notify_tx.indication, event->notify_tx.status); // This event corresponds to either a sent notify/indicate (status == 0), or an indication confirmation (status != 0). if (event->notify_tx.indication && event->notify_tx.status != 0) { // Map "done/ack" to 0, otherwise pass the status directly. @@ -263,7 +265,7 @@ STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { } int mp_bluetooth_init(void) { - DEBUG_EVENT_printf("mp_bluetooth_init\n"); + DEBUG_printf("mp_bluetooth_init\n"); // Clean up if necessary. mp_bluetooth_deinit(); @@ -291,7 +293,7 @@ int mp_bluetooth_init(void) { while (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE) { MICROPY_EVENT_POLL_HOOK } - DEBUG_EVENT_printf("mp_bluetooth_init: ready\n"); + DEBUG_printf("mp_bluetooth_init: ready\n"); return 0; } @@ -304,7 +306,7 @@ STATIC void ble_hs_shutdown_stop_cb(int status, void *arg) { STATIC struct ble_hs_stop_listener ble_hs_shutdown_stop_listener; void mp_bluetooth_deinit(void) { - DEBUG_EVENT_printf("mp_bluetooth_deinit\n"); + DEBUG_printf("mp_bluetooth_deinit\n"); if (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { return; } @@ -325,7 +327,7 @@ void mp_bluetooth_deinit(void) { mp_bluetooth_nimble_port_deinit(); MP_STATE_PORT(bluetooth_nimble_root_pointers) = NULL; - DEBUG_EVENT_printf("mp_bluetooth_deinit: shut down\n"); + DEBUG_printf("mp_bluetooth_deinit: shut down\n"); } bool mp_bluetooth_is_active(void) { @@ -409,7 +411,7 @@ int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, cons if (ret == 0) { return 0; } - DEBUG_EVENT_printf("ble_gap_adv_start: %d\n", ret); + DEBUG_printf("ble_gap_adv_start: %d\n", ret); return ble_hs_err_to_errno(ret); } @@ -421,7 +423,7 @@ void mp_bluetooth_gap_advertise_stop(void) { } static int characteristic_access_cb(uint16_t conn_handle, uint16_t value_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { - DEBUG_EVENT_printf("characteristic_access_cb: conn_handle=%u value_handle=%u op=%u\n", conn_handle, value_handle, ctxt->op); + DEBUG_printf("characteristic_access_cb: conn_handle=%u value_handle=%u op=%u\n", conn_handle, value_handle, ctxt->op); if (!mp_bluetooth_is_active()) { return 0; } @@ -640,7 +642,7 @@ STATIC void gattc_on_data_available(uint8_t event, uint16_t conn_handle, uint16_ } STATIC int gap_scan_cb(struct ble_gap_event *event, void *arg) { - DEBUG_EVENT_printf("gap_scan_cb: event=%d type=%d\n", event->type, event->type == BLE_GAP_EVENT_DISC ? event->disc.event_type : -1); + DEBUG_printf("gap_scan_cb: event=%d type=%d\n", event->type, event->type == BLE_GAP_EVENT_DISC ? event->disc.event_type : -1); if (!mp_bluetooth_is_active()) { return 0; } @@ -681,6 +683,7 @@ int mp_bluetooth_gap_scan_start(int32_t duration_ms, int32_t interval_us, int32_ } int mp_bluetooth_gap_scan_stop(void) { + DEBUG_printf("mp_bluetooth_gap_scan_stop\n"); if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } @@ -697,7 +700,7 @@ int mp_bluetooth_gap_scan_stop(void) { // Central role: GAP events for a connected peripheral. STATIC int peripheral_gap_event_cb(struct ble_gap_event *event, void *arg) { - DEBUG_EVENT_printf("peripheral_gap_event_cb: event=%d\n", event->type); + DEBUG_printf("peripheral_gap_event_cb: event=%d\n", event->type); if (!mp_bluetooth_is_active()) { return 0; } @@ -745,6 +748,7 @@ STATIC int peripheral_gap_event_cb(struct ble_gap_event *event, void *arg) { } int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, int32_t duration_ms) { + DEBUG_printf("mp_bluetooth_gap_peripheral_connect\n"); if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } @@ -770,7 +774,7 @@ int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, } STATIC int peripheral_discover_service_cb(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_svc *service, void *arg) { - DEBUG_EVENT_printf("peripheral_discover_service_cb: conn_handle=%d status=%d start_handle=%d\n", conn_handle, error->status, service ? service->start_handle : -1); + DEBUG_printf("peripheral_discover_service_cb: conn_handle=%d status=%d start_handle=%d\n", conn_handle, error->status, service ? service->start_handle : -1); if (!mp_bluetooth_is_active()) { return 0; } @@ -799,7 +803,7 @@ int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle, const mp_ } STATIC int ble_gatt_characteristic_cb(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_chr *characteristic, void *arg) { - DEBUG_EVENT_printf("ble_gatt_characteristic_cb: conn_handle=%d status=%d def_handle=%d val_handle=%d\n", conn_handle, error->status, characteristic ? characteristic->def_handle : -1, characteristic ? characteristic->val_handle : -1); + DEBUG_printf("ble_gatt_characteristic_cb: conn_handle=%d status=%d def_handle=%d val_handle=%d\n", conn_handle, error->status, characteristic ? characteristic->def_handle : -1, characteristic ? characteristic->val_handle : -1); if (!mp_bluetooth_is_active()) { return 0; } @@ -828,7 +832,7 @@ int mp_bluetooth_gattc_discover_characteristics(uint16_t conn_handle, uint16_t s } STATIC int ble_gatt_descriptor_cb(uint16_t conn_handle, const struct ble_gatt_error *error, uint16_t characteristic_val_handle, const struct ble_gatt_dsc *descriptor, void *arg) { - DEBUG_EVENT_printf("ble_gatt_descriptor_cb: conn_handle=%d status=%d chr_handle=%d dsc_handle=%d\n", conn_handle, error->status, characteristic_val_handle, descriptor ? descriptor->handle : -1); + DEBUG_printf("ble_gatt_descriptor_cb: conn_handle=%d status=%d chr_handle=%d dsc_handle=%d\n", conn_handle, error->status, characteristic_val_handle, descriptor ? descriptor->handle : -1); if (!mp_bluetooth_is_active()) { return 0; } @@ -850,7 +854,7 @@ int mp_bluetooth_gattc_discover_descriptors(uint16_t conn_handle, uint16_t start } STATIC int ble_gatt_attr_read_cb(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg) { - DEBUG_EVENT_printf("ble_gatt_attr_read_cb: conn_handle=%d status=%d handle=%d\n", conn_handle, error->status, attr ? attr->handle : -1); + DEBUG_printf("ble_gatt_attr_read_cb: conn_handle=%d status=%d handle=%d\n", conn_handle, error->status, attr ? attr->handle : -1); if (!mp_bluetooth_is_active()) { return 0; } @@ -871,7 +875,7 @@ int mp_bluetooth_gattc_read(uint16_t conn_handle, uint16_t value_handle) { } STATIC int ble_gatt_attr_write_cb(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg) { - DEBUG_EVENT_printf("ble_gatt_attr_write_cb: conn_handle=%d status=%d handle=%d\n", conn_handle, error->status, attr ? attr->handle : -1); + DEBUG_printf("ble_gatt_attr_write_cb: conn_handle=%d status=%d handle=%d\n", conn_handle, error->status, attr ? attr->handle : -1); if (!mp_bluetooth_is_active()) { return 0; } From ed14435a8e6199b845c3404a9052f9ff4213292c Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 14 Aug 2020 15:43:09 +1000 Subject: [PATCH 008/337] extmod/modbluetooth: Refactor stack/hci/driver/port bindings. Previously the interaction between the different layers of the Bluetooth stack was different on each port and each stack. This commit defines common interfaces between them and implements them for cyw43, btstack, nimble, stm32, unix. --- drivers/cyw43/cywbt.c | 14 +- extmod/btstack/btstack.mk | 11 ++ extmod/btstack/btstack_config.h | 1 + .../btstack/btstack_hci_uart.c | 94 ++++---------- .../btstack_hci_uart.h} | 21 ++- extmod/btstack/modbluetooth_btstack.c | 5 + extmod/btstack/modbluetooth_btstack.h | 1 + extmod/mpbthci.c | 38 ++++++ extmod/{modbluetooth_hci.h => mpbthci.h} | 25 ++-- extmod/nimble/hal/hal_uart.c | 25 ++-- extmod/nimble/hal/hal_uart.h | 31 ++++- extmod/nimble/logcfg/logcfg.h | 45 +++++++ extmod/nimble/modbluetooth_nimble.c | 122 ++++++++++++++---- extmod/nimble/modbluetooth_nimble.h | 18 ++- extmod/nimble/nimble.mk | 13 +- .../nimble/{npl_os.c => nimble_npl_os.c} | 64 ++++++--- extmod/nimble/nimble/nimble_npl_os.h | 19 ++- extmod/nimble/syscfg/syscfg.h | 5 +- ports/esp32/Makefile | 2 +- ports/esp32/{nimble.c => mpnimbleport.c} | 30 ++++- ports/stm32/Makefile | 7 +- .../{modbluetooth_hci.c => mpbthciport.c} | 97 +++++++++----- ports/stm32/mpbtstackport.c | 107 +++++++++++++++ ports/stm32/mpbtstackport.h | 34 +++++ ports/stm32/mpconfigport.h | 21 ++- ports/stm32/{nimble.c => mpnimbleport.c} | 69 ++++------ ports/stm32/mpnimbleport.h | 34 +++++ ports/unix/Makefile | 9 +- ports/unix/mpbtstackport.h | 39 ++++++ ports/unix/mpbtstackport_common.c | 92 +++++++++++++ .../{btstack_usb.c => mpbtstackport_usb.c} | 101 ++------------- ports/unix/mpconfigport.h | 4 +- 32 files changed, 853 insertions(+), 345 deletions(-) rename ports/stm32/btstack.c => extmod/btstack/btstack_hci_uart.c (67%) rename extmod/{nimble/nimble/nimble_hci_uart.h => btstack/btstack_hci_uart.h} (66%) create mode 100644 extmod/mpbthci.c rename extmod/{modbluetooth_hci.h => mpbthci.h} (71%) create mode 100644 extmod/nimble/logcfg/logcfg.h rename extmod/nimble/nimble/{npl_os.c => nimble_npl_os.c} (85%) rename ports/esp32/{nimble.c => mpnimbleport.c} (66%) rename ports/stm32/{modbluetooth_hci.c => mpbthciport.c} (75%) create mode 100644 ports/stm32/mpbtstackport.c create mode 100644 ports/stm32/mpbtstackport.h rename ports/stm32/{nimble.c => mpnimbleport.c} (56%) create mode 100644 ports/stm32/mpnimbleport.h create mode 100644 ports/unix/mpbtstackport.h create mode 100644 ports/unix/mpbtstackport_common.c rename ports/unix/{btstack_usb.c => mpbtstackport_usb.c} (54%) diff --git a/drivers/cyw43/cywbt.c b/drivers/cyw43/cywbt.c index 79318f4483..defe670683 100644 --- a/drivers/cyw43/cywbt.c +++ b/drivers/cyw43/cywbt.c @@ -31,13 +31,19 @@ #include "py/mphal.h" #include "pin_static_af.h" #include "uart.h" -#include "extmod/modbluetooth_hci.h" +#include "extmod/mpbthci.h" #if MICROPY_PY_NETWORK_CYW43 extern const char fw_4343WA1_7_45_98_50_start; #define CYWBT_FW_ADDR (&fw_4343WA1_7_45_98_50_start + 749 * 512 + 29 * 256) +// Provided by the port. +extern pyb_uart_obj_t mp_bluetooth_hci_uart_obj; + +// Provided by the port, and also possibly shared with the stack. +extern uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; + /******************************************************************************/ // CYW BT HCI low-level driver @@ -168,10 +174,6 @@ int mp_bluetooth_hci_controller_init(void) { mp_hal_pin_config(pyb_pin_WL_GPIO_4, MP_HAL_PIN_MODE_OUTPUT, MP_HAL_PIN_PULL_NONE, 0); // RF-switch power mp_hal_pin_high(pyb_pin_WL_GPIO_4); // Turn the RF-switch on - return 0; -} - -int mp_bluetooth_hci_controller_activate(void) { uint8_t buf[256]; mp_hal_pin_low(pyb_pin_BT_REG_ON); @@ -219,7 +221,7 @@ int mp_bluetooth_hci_controller_activate(void) { return 0; } -int mp_bluetooth_hci_controller_deactivate(void) { +int mp_bluetooth_hci_controller_deinit(void) { mp_hal_pin_low(pyb_pin_BT_REG_ON); return 0; diff --git a/extmod/btstack/btstack.mk b/extmod/btstack/btstack.mk index dd96e63379..3084601b85 100644 --- a/extmod/btstack/btstack.mk +++ b/extmod/btstack/btstack.mk @@ -38,6 +38,17 @@ CFLAGS += $(shell pkg-config libusb-1.0 --cflags) LDFLAGS += $(shell pkg-config libusb-1.0 --libs) endif +ifeq ($(MICROPY_BLUETOOTH_BTSTACK_H4),1) +SRC_BTSTACK += \ + lib/btstack/src/hci_transport_h4.c \ + lib/btstack/chipset/zephyr/btstack_chipset_zephyr.c + +EXTMOD_SRC_C += \ + extmod/btstack/btstack_hci_uart.c \ + +CFLAGS_MOD += -DMICROPY_BLUETOOTH_BTSTACK_H4=1 +endif + ifeq ($(MICROPY_BLUETOOTH_BTSTACK_ENABLE_CLASSIC),1) include $(BTSTACK_DIR)/src/classic/Makefile.inc SRC_BTSTACK += \ diff --git a/extmod/btstack/btstack_config.h b/extmod/btstack/btstack_config.h index f420f47a5b..e56a84f94a 100644 --- a/extmod/btstack/btstack_config.h +++ b/extmod/btstack/btstack_config.h @@ -8,6 +8,7 @@ // #define ENABLE_CLASSIC #define ENABLE_LE_DATA_CHANNELS // #define ENABLE_LOG_INFO +// #define ENABLE_LOG_DEBUG #define ENABLE_LOG_ERROR // BTstack configuration. buffers, sizes, ... diff --git a/ports/stm32/btstack.c b/extmod/btstack/btstack_hci_uart.c similarity index 67% rename from ports/stm32/btstack.c rename to extmod/btstack/btstack_hci_uart.c index cbb15a86cb..5c96e02dc0 100644 --- a/ports/stm32/btstack.c +++ b/extmod/btstack/btstack_hci_uart.c @@ -4,6 +4,7 @@ * 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 @@ -31,12 +32,14 @@ #if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK #include "lib/btstack/src/btstack.h" -#include "lib/btstack/platform/embedded/btstack_run_loop_embedded.h" -#include "lib/btstack/platform/embedded/hal_cpu.h" -#include "lib/btstack/platform/embedded/hal_time_ms.h" -#include "extmod/modbluetooth_hci.h" -#include "extmod/btstack/modbluetooth_btstack.h" +#include "extmod/mpbthci.h" +#include "extmod/btstack/btstack_hci_uart.h" + +#include "mpbtstackport.h" + +// Implements a btstack btstack_uart_block_t on top of the mphciuart.h +// interface to an HCI UART provided by the port. // We pass the bytes directly to the UART during a send, but then notify btstack in the next poll. STATIC bool send_done; @@ -48,42 +51,29 @@ STATIC size_t recv_len; STATIC size_t recv_idx; STATIC void (*recv_handler)(void); -// The IRQ functionality in btstack_run_loop_embedded.c is not used, so the -// following three functions are empty. - -void hal_cpu_disable_irqs(void) { -} - -void hal_cpu_enable_irqs(void) { -} - -void hal_cpu_enable_irqs_and_sleep(void) { -} - -uint32_t hal_time_ms(void) { - return mp_hal_ticks_ms(); -} - STATIC int btstack_uart_init(const btstack_uart_config_t *uart_config) { + (void)uart_config; + send_done = false; recv_len = 0; recv_idx = 0; recv_handler = NULL; send_handler = NULL; - // Set up the UART periperhal. - mp_bluetooth_hci_uart_init(MICROPY_HW_BLE_UART_ID); + // Set up the UART peripheral, attach IRQ and power up the HCI controller. + // We haven't been told the baud rate yet, so defer that until btstack_uart_set_baudrate. + mp_bluetooth_hci_uart_init(MICROPY_HW_BLE_UART_ID, 0); + mp_bluetooth_hci_controller_init(); return 0; } STATIC int btstack_uart_open(void) { - // Attach IRQ and power up the HCI controller. - mp_bluetooth_hci_uart_activate(); return 0; } STATIC int btstack_uart_close(void) { + mp_bluetooth_hci_controller_deinit(); return 0; } @@ -101,10 +91,12 @@ STATIC int btstack_uart_set_baudrate(uint32_t baudrate) { } STATIC int btstack_uart_set_parity(int parity) { + (void)parity; return 0; } STATIC int btstack_uart_set_flowcontrol(int flowcontrol) { + (void)flowcontrol; return 0; } @@ -123,14 +115,16 @@ STATIC int btstack_uart_get_supported_sleep_modes(void) { } STATIC void btstack_uart_set_sleep(btstack_uart_sleep_mode_t sleep_mode) { + (void)sleep_mode; // printf("btstack_uart_set_sleep %u\n", sleep_mode); } STATIC void btstack_uart_set_wakeup_handler(void (*wakeup_handler)(void)) { + (void)wakeup_handler; // printf("btstack_uart_set_wakeup_handler\n"); } -STATIC const btstack_uart_block_t btstack_uart_block = { +const btstack_uart_block_t mp_bluetooth_btstack_hci_uart_block = { &btstack_uart_init, &btstack_uart_open, &btstack_uart_close, @@ -146,15 +140,9 @@ STATIC const btstack_uart_block_t btstack_uart_block = { &btstack_uart_set_wakeup_handler, }; -STATIC const hci_transport_config_uart_t hci_transport_config_uart = { - HCI_TRANSPORT_CONFIG_UART, - MICROPY_HW_BLE_UART_BAUDRATE, - 3000000, - 0, - NULL, -}; +void mp_bluetooth_btstack_hci_uart_process(void) { + bool host_wake = mp_bluetooth_hci_controller_woken(); -STATIC void btstack_uart_process(void) { if (send_done) { // If we'd done a TX in the last interval, notify btstack that it's complete. send_done = false; @@ -176,48 +164,10 @@ STATIC void btstack_uart_process(void) { } } } -} -void mp_bluetooth_hci_poll(void) { - if (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_OFF) { - return; - } - - // Process uart data. - bool host_wake = mp_bluetooth_hci_controller_woken(); - btstack_uart_process(); if (host_wake) { mp_bluetooth_hci_controller_sleep_maybe(); } - - // Call the BTstack run loop. - btstack_run_loop_embedded_execute_once(); -} - -void mp_bluetooth_btstack_port_init(void) { - static bool run_loop_init = false; - if (!run_loop_init) { - run_loop_init = true; - btstack_run_loop_init(btstack_run_loop_embedded_get_instance()); - } else { - btstack_run_loop_embedded_get_instance()->init(); - } - - // hci_dump_open(NULL, HCI_DUMP_STDOUT); - const hci_transport_t *transport = hci_transport_h4_instance(&btstack_uart_block); - hci_init(transport, &hci_transport_config_uart); - - // TODO: Probably not necessary for BCM (we have our own firmware loader), - // but might be worth investigating for other controllers in the future. - // hci_set_chipset(btstack_chipset_bcm_instance()); -} - -void mp_bluetooth_btstack_port_deinit(void) { - hci_power_control(HCI_POWER_OFF); -} - -void mp_bluetooth_btstack_port_start(void) { - hci_power_control(HCI_POWER_ON); } #endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK diff --git a/extmod/nimble/nimble/nimble_hci_uart.h b/extmod/btstack/btstack_hci_uart.h similarity index 66% rename from extmod/nimble/nimble/nimble_hci_uart.h rename to extmod/btstack/btstack_hci_uart.h index 646a12dac1..8011e587de 100644 --- a/extmod/nimble/nimble/nimble_hci_uart.h +++ b/extmod/btstack/btstack_hci_uart.h @@ -3,7 +3,8 @@ * * The MIT License (MIT) * - * Copyright (c) 2019 Damien P. George + * 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 @@ -23,18 +24,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_EXTMOD_NIMBLE_NIMBLE_NIMBLE_HCI_UART_H -#define MICROPY_INCLUDED_EXTMOD_NIMBLE_NIMBLE_NIMBLE_HCI_UART_H -// Extensions to extmod/modbluetooth_hci.h specific to NimBLE. +#ifndef MICROPY_INCLUDED_EXTMOD_BTSTACK_HCI_UART_H +#define MICROPY_INCLUDED_EXTMOD_BTSTACK_HCI_UART_H -#include "extmod/nimble/hal/hal_uart.h" +#include "lib/btstack/src/btstack.h" -// Helpers called from ports. -void mp_bluetooth_nimble_hci_uart_process(void); +// --- Used by the port to create the HCI transport --------------------------- +extern const btstack_uart_block_t mp_bluetooth_btstack_hci_uart_block; -// Must be provided by the port. -void mp_bluetooth_nimble_hci_uart_rx(hal_uart_rx_cb_t rx_cb, void *rx_arg); -void mp_bluetooth_nimble_hci_uart_tx_strn(const char *str, uint len); +// --- Called by the MicroPython port when UART data is available ------------- +void mp_bluetooth_btstack_hci_uart_process(void); -#endif // MICROPY_INCLUDED_EXTMOD_NIMBLE_NIMBLE_NIMBLE_HCI_UART_H +#endif // MICROPY_INCLUDED_EXTMOD_BTSTACK_MODBLUETOOTH_BTSTACK_H diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index 9aef7e6879..03ec12b734 100644 --- a/extmod/btstack/modbluetooth_btstack.c +++ b/extmod/btstack/modbluetooth_btstack.c @@ -53,6 +53,8 @@ STATIC const uint16_t BTSTACK_GAP_DEVICE_NAME_HANDLE = 3; volatile int mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; +#define ERRNO_BLUETOOTH_NOT_ACTIVE MP_ENODEV + STATIC int btstack_error_to_errno(int err) { DEBUG_printf(" --> btstack error: %d\n", err); if (err == ERROR_CODE_SUCCESS) { @@ -300,6 +302,9 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t if (state == HCI_STATE_WORKING) { // Signal that initialisation has completed. mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_ACTIVE; + } else if (state == HCI_STATE_HALTING) { + // Signal that de-initialisation has begun. + mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_HALTING; } else if (state == HCI_STATE_OFF) { // Signal that de-initialisation has completed. mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; diff --git a/extmod/btstack/modbluetooth_btstack.h b/extmod/btstack/modbluetooth_btstack.h index 2fad86f226..7890bbfae2 100644 --- a/extmod/btstack/modbluetooth_btstack.h +++ b/extmod/btstack/modbluetooth_btstack.h @@ -56,6 +56,7 @@ enum { MP_BLUETOOTH_BTSTACK_STATE_OFF, MP_BLUETOOTH_BTSTACK_STATE_STARTING, MP_BLUETOOTH_BTSTACK_STATE_ACTIVE, + MP_BLUETOOTH_BTSTACK_STATE_HALTING, MP_BLUETOOTH_BTSTACK_STATE_TIMEOUT, }; diff --git a/extmod/mpbthci.c b/extmod/mpbthci.c new file mode 100644 index 0000000000..79a8654242 --- /dev/null +++ b/extmod/mpbthci.c @@ -0,0 +1,38 @@ +/* + * 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. + */ + +// Default definitions in case a controller doesn't implement this. + +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH + +#define DEBUG_printf(...) // printf(__VA_ARGS__) + +#include "extmod/mpbthci.h" + + +#endif // MICROPY_PY_BLUETOOTH diff --git a/extmod/modbluetooth_hci.h b/extmod/mpbthci.h similarity index 71% rename from extmod/modbluetooth_hci.h rename to extmod/mpbthci.h index d6b432d224..acb5b832ba 100644 --- a/extmod/modbluetooth_hci.h +++ b/extmod/mpbthci.h @@ -24,15 +24,15 @@ * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_EXTMOD_MODBLUETOOTH_HCI_H -#define MICROPY_INCLUDED_EXTMOD_MODBLUETOOTH_HCI_H +#ifndef MICROPY_INCLUDED_EXTMOD_MPBTHCI_H +#define MICROPY_INCLUDED_EXTMOD_MPBTHCI_H -#include "uart.h" +// --- Optionally can be implemented by the driver. --------------------------- -// Optionally can be implemented by the driver. +// Start/stop the HCI controller. +// Requires the UART to this HCI controller is available. int mp_bluetooth_hci_controller_init(void); -int mp_bluetooth_hci_controller_activate(void); -int mp_bluetooth_hci_controller_deactivate(void); +int mp_bluetooth_hci_controller_deinit(void); // Tell the controller to go to sleep (e.g. on RX if we don't think we're expecting anything more). int mp_bluetooth_hci_controller_sleep_maybe(void); @@ -41,16 +41,11 @@ bool mp_bluetooth_hci_controller_woken(void); // Wake up the controller (e.g. we're about to TX). int mp_bluetooth_hci_controller_wakeup(void); -// Storage and bindings that need to be implemented by the port. -// These are used by the stack bindings (e.g. nimble/hal_uart.c) -// as well as potentially the driver (e.g. cywbt.c). -extern uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; -extern pyb_uart_obj_t mp_bluetooth_hci_uart_obj; - -int mp_bluetooth_hci_uart_init(uint32_t port); -int mp_bluetooth_hci_uart_activate(void); +// --- Bindings that need to be implemented by the port. ---------------------- +int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate); +int mp_bluetooth_hci_uart_deinit(void); int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate); int mp_bluetooth_hci_uart_readchar(void); int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len); -#endif // MICROPY_INCLUDED_EXTMOD_MODBLUETOOTH_HCI_H +#endif // MICROPY_INCLUDED_EXTMOD_MPBTHCI_H diff --git a/extmod/nimble/hal/hal_uart.c b/extmod/nimble/hal/hal_uart.c index fba82b8304..c6d0850fea 100644 --- a/extmod/nimble/hal/hal_uart.c +++ b/extmod/nimble/hal/hal_uart.c @@ -26,11 +26,9 @@ #include "py/runtime.h" #include "py/mphal.h" -#include "pin_static_af.h" #include "nimble/ble.h" #include "extmod/nimble/hal/hal_uart.h" -#include "extmod/modbluetooth_hci.h" -#include "extmod/nimble/nimble/nimble_hci_uart.h" +#include "extmod/mpbthci.h" #if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE @@ -39,6 +37,9 @@ static void *hal_uart_tx_arg; static hal_uart_rx_cb_t hal_uart_rx_cb; static void *hal_uart_rx_arg; +// Provided by the port, and also possibly shared with the driver. +extern uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; + int hal_uart_init_cbs(uint32_t port, hal_uart_tx_cb_t tx_cb, void *tx_arg, hal_uart_rx_cb_t rx_cb, void *rx_arg) { hal_uart_tx_cb = tx_cb; hal_uart_tx_arg = tx_arg; @@ -48,9 +49,7 @@ int hal_uart_init_cbs(uint32_t port, hal_uart_tx_cb_t tx_cb, void *tx_arg, hal_u } int hal_uart_config(uint32_t port, uint32_t baudrate, uint32_t bits, uint32_t stop, uint32_t parity, uint32_t flow) { - mp_bluetooth_hci_uart_init(port); - mp_bluetooth_hci_uart_set_baudrate(baudrate); - return mp_bluetooth_hci_uart_activate(); + return mp_bluetooth_hci_uart_init(port, baudrate); } void hal_uart_start_tx(uint32_t port) { @@ -71,7 +70,7 @@ void hal_uart_start_tx(uint32_t port) { printf("\n"); #endif - mp_bluetooth_nimble_hci_uart_tx_strn((void*)mp_bluetooth_hci_cmd_buf, len); + mp_bluetooth_hci_uart_write(mp_bluetooth_hci_cmd_buf, len); } int hal_uart_close(uint32_t port) { @@ -79,7 +78,17 @@ int hal_uart_close(uint32_t port) { } void mp_bluetooth_nimble_hci_uart_process(void) { - mp_bluetooth_nimble_hci_uart_rx(hal_uart_rx_cb, hal_uart_rx_arg); + bool host_wake = mp_bluetooth_hci_controller_woken(); + + int chr; + while ((chr = mp_bluetooth_hci_uart_readchar()) >= 0) { + // printf("UART RX: %02x\n", data); + hal_uart_rx_cb(hal_uart_rx_arg, chr); + } + + if (host_wake) { + mp_bluetooth_hci_controller_sleep_maybe(); + } } #endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE diff --git a/extmod/nimble/hal/hal_uart.h b/extmod/nimble/hal/hal_uart.h index 0ef04bc81e..1ff1c17436 100644 --- a/extmod/nimble/hal/hal_uart.h +++ b/extmod/nimble/hal/hal_uart.h @@ -1,3 +1,29 @@ +/* + * 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. + */ + #ifndef MICROPY_INCLUDED_EXTMOD_NIMBLE_HAL_HAL_UART_H #define MICROPY_INCLUDED_EXTMOD_NIMBLE_HAL_HAL_UART_H @@ -10,10 +36,13 @@ typedef int (*hal_uart_tx_cb_t)(void *arg); typedef int (*hal_uart_rx_cb_t)(void *arg, uint8_t data); -// Called by NimBLE, implemented in hal_uart.c. +// --- Called by NimBLE, implemented in hal_uart.c. --------------------------- int hal_uart_init_cbs(uint32_t port, hal_uart_tx_cb_t tx_cb, void *tx_arg, hal_uart_rx_cb_t rx_cb, void *rx_arg); int hal_uart_config(uint32_t port, uint32_t baud, uint32_t bits, uint32_t stop, uint32_t parity, uint32_t flow); 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); + #endif // MICROPY_INCLUDED_EXTMOD_NIMBLE_HAL_HAL_UART_H diff --git a/extmod/nimble/logcfg/logcfg.h b/extmod/nimble/logcfg/logcfg.h new file mode 100644 index 0000000000..e9023dae65 --- /dev/null +++ b/extmod/nimble/logcfg/logcfg.h @@ -0,0 +1,45 @@ +/** + * This file was generated by Apache newt version: 1.8.0-dev + */ + +#ifndef MICROPY_INCLUDED_EXTMOD_NIMBLE_LOGCFG_LOGCFG_H +#define MICROPY_INCLUDED_EXTMOD_NIMBLE_LOGCFG_LOGCFG_H + +#include "py/mphal.h" +#include "modlog/modlog.h" +#include "log_common/log_common.h" + +#define MICROPY_PY_BLUETOOTH_DIAGNOSTIC_LOGGING (1) + +#if MICROPY_PY_BLUETOOTH_DIAGNOSTIC_LOGGING +#define DFLT_LOG_DEBUG(...) MODLOG_DEBUG(4, __VA_ARGS__) +#else +#define DFLT_LOG_DEBUG(...) IGNORE(__VA_ARGS__) +#endif + +#if MICROPY_PY_BLUETOOTH_DIAGNOSTIC_LOGGING > 1 +#define BLE_HS_LOG_DEBUG(...) MODLOG_DEBUG(4, __VA_ARGS__) +#else +#define BLE_HS_LOG_DEBUG(...) IGNORE(__VA_ARGS__) +#endif + +#define BLE_HS_LOG_INFO(...) MODLOG_INFO(4, __VA_ARGS__) +#define BLE_HS_LOG_WARN(...) MODLOG_WARN(4, __VA_ARGS__) +#define BLE_HS_LOG_ERROR(...) MODLOG_ERROR(4, __VA_ARGS__) +#define BLE_HS_LOG_CRITICAL(...) MODLOG_CRITICAL(4, __VA_ARGS__) +#define BLE_HS_LOG_DISABLED(...) MODLOG_DISABLED(4, __VA_ARGS__) + +#define DFLT_LOG_INFO(...) MODLOG_INFO(0, __VA_ARGS__) +#define DFLT_LOG_WARN(...) MODLOG_WARN(0, __VA_ARGS__) +#define DFLT_LOG_ERROR(...) MODLOG_ERROR(0, __VA_ARGS__) +#define DFLT_LOG_CRITICAL(...) MODLOG_CRITICAL(0, __VA_ARGS__) +#define DFLT_LOG_DISABLED(...) MODLOG_DISABLED(0, __VA_ARGS__) + +#define MFG_LOG_DEBUG(...) IGNORE(__VA_ARGS__) +#define MFG_LOG_INFO(...) IGNORE(__VA_ARGS__) +#define MFG_LOG_WARN(...) IGNORE(__VA_ARGS__) +#define MFG_LOG_ERROR(...) IGNORE(__VA_ARGS__) +#define MFG_LOG_CRITICAL(...) IGNORE(__VA_ARGS__) +#define MFG_LOG_DISABLED(...) MODLOG_DISABLED(128, __VA_ARGS__) + +#endif // MICROPY_INCLUDED_EXTMOD_NIMBLE_LOGCFG_LOGCFG_H diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 36bfa6cccf..61ba3a3aba 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -33,6 +33,7 @@ #include "extmod/nimble/modbluetooth_nimble.h" #include "extmod/modbluetooth.h" +#include "extmod/mpbthci.h" #include "host/ble_hs.h" #include "host/util/util.h" @@ -64,10 +65,11 @@ STATIC int8_t ble_hs_err_to_errno_table[] = { }; STATIC int ble_hs_err_to_errno(int err) { + DEBUG_printf("ble_hs_err_to_errno: %d\n", err); if (!err) { return 0; } - if (0 <= err && err < MP_ARRAY_SIZE(ble_hs_err_to_errno_table) && ble_hs_err_to_errno_table[err]) { + if (err >= 0 && (unsigned)err < MP_ARRAY_SIZE(ble_hs_err_to_errno_table) && ble_hs_err_to_errno_table[err]) { return ble_hs_err_to_errno_table[err]; } else { return MP_EIO; @@ -150,6 +152,12 @@ STATIC void sync_cb(void) { int rc; ble_addr_t addr; + DEBUG_printf("sync_cb: state=%d\n", mp_bluetooth_nimble_ble_state); + + if (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC) { + return; + } + rc = ble_hs_util_ensure_addr(0); // prefer public address if (rc != 0) { // https://mynewt.apache.org/latest/tutorials/ble/eddystone.html#configure-the-nimble-stack-with-an-address @@ -264,11 +272,67 @@ STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { return 0; } +#if !MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY + +// On ports such as ESP32 where we only implement the bindings, then +// the port must provide these functions. +// But for STM32 / Unix-H4, we provide a default implementation of the +// port-specific functionality. +// TODO: In the future if a port ever needs to customise these functions +// then investigate using MP_WEAK or splitting them out to another .c file. + +#include "transport/uart/ble_hci_uart.h" + +void mp_bluetooth_nimble_port_hci_init(void) { + DEBUG_printf("mp_bluetooth_nimble_port_hci_init (nimble default)\n"); + // This calls mp_bluetooth_hci_uart_init (via ble_hci_uart_init --> hal_uart_config --> mp_bluetooth_hci_uart_init). + ble_hci_uart_init(); + mp_bluetooth_hci_controller_init(); +} + +void mp_bluetooth_nimble_port_hci_deinit(void) { + DEBUG_printf("mp_bluetooth_nimble_port_hci_deinit (nimble default)\n"); + mp_bluetooth_hci_controller_deinit(); + mp_bluetooth_hci_uart_deinit(); +} + +void mp_bluetooth_nimble_port_start(void) { + DEBUG_printf("mp_bluetooth_nimble_port_start (nimble default)\n"); + // By default, assume port is already running its own background task (e.g. SysTick on STM32). + // ESP32 runs a FreeRTOS task, Unix has a thread. +} + +// Called when the host stop procedure has completed. +STATIC void ble_hs_shutdown_stop_cb(int status, void *arg) { + (void)status; + (void)arg; + mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF; +} + +STATIC struct ble_hs_stop_listener ble_hs_shutdown_stop_listener; + +void mp_bluetooth_nimble_port_shutdown(void) { + DEBUG_printf("mp_bluetooth_nimble_port_shutdown (nimble default)\n"); + // By default, just call ble_hs_stop directly and wait for the stack to stop. + + mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_STOPPING; + + ble_hs_stop(&ble_hs_shutdown_stop_listener, ble_hs_shutdown_stop_cb, NULL); + + while (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { + MICROPY_EVENT_POLL_HOOK + } +} + +#endif // !MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY + int mp_bluetooth_init(void) { DEBUG_printf("mp_bluetooth_init\n"); // Clean up if necessary. mp_bluetooth_deinit(); + mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_STARTING; + ble_hs_cfg.reset_cb = reset_cb; ble_hs_cfg.sync_cb = sync_cb; ble_hs_cfg.gatts_register_cb = gatts_register_cb; @@ -277,56 +341,70 @@ int mp_bluetooth_init(void) { 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); - mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_STARTING; + #if !MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY + // Dereference any previous NimBLE mallocs. + MP_STATE_PORT(bluetooth_nimble_memory) = NULL; + #endif - mp_bluetooth_nimble_port_preinit(); + // Allow port (ESP32) to override NimBLE's HCI init. + // Otherwise default implementation above calls ble_hci_uart_init(). + mp_bluetooth_nimble_port_hci_init(); + + // Initialise NimBLE memory and data structures. nimble_port_init(); - mp_bluetooth_nimble_port_postinit(); // By default, just register the default gap/gatt service. ble_svc_gap_init(); ble_svc_gatt_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; + // Wait for sync callback while (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE) { MICROPY_EVENT_POLL_HOOK } + DEBUG_printf("mp_bluetooth_init: ready\n"); return 0; } -// Called when the host stop procedure has completed. -STATIC void ble_hs_shutdown_stop_cb(int status, void *arg) { - mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF; -} - -STATIC struct ble_hs_stop_listener ble_hs_shutdown_stop_listener; - void mp_bluetooth_deinit(void) { DEBUG_printf("mp_bluetooth_deinit\n"); if (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { return; } - mp_bluetooth_gap_advertise_stop(); - #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE - mp_bluetooth_gap_scan_stop(); - #endif + // Must call ble_hs_stop() in a port-specific way to stop the background + // task. Default implementation provided above. + if (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE) { + mp_bluetooth_gap_advertise_stop(); + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE + mp_bluetooth_gap_scan_stop(); + #endif - mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_STOPPING; + DEBUG_printf("mp_bluetooth_deinit: starting port shutdown\n"); - ble_hs_stop(&ble_hs_shutdown_stop_listener, ble_hs_shutdown_stop_cb, NULL); - - while (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { - MICROPY_EVENT_POLL_HOOK + mp_bluetooth_nimble_port_shutdown(); + assert(mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF); + } else { + mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF; } - mp_bluetooth_nimble_port_deinit(); + // Shutdown the HCI controller. + mp_bluetooth_nimble_port_hci_deinit(); MP_STATE_PORT(bluetooth_nimble_root_pointers) = NULL; + + #if !MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY + // Dereference any previous NimBLE mallocs. + MP_STATE_PORT(bluetooth_nimble_memory) = NULL; + #endif + DEBUG_printf("mp_bluetooth_deinit: shut down\n"); } @@ -485,7 +563,7 @@ int mp_bluetooth_gatts_register_service_begin(bool append) { if (!append) { // Unref any previous service definitions. - for (int i = 0; i < MP_STATE_PORT(bluetooth_nimble_root_pointers)->n_services; ++i) { + 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; diff --git a/extmod/nimble/modbluetooth_nimble.h b/extmod/nimble/modbluetooth_nimble.h index f44e1d69dc..7e401781e7 100644 --- a/extmod/nimble/modbluetooth_nimble.h +++ b/extmod/nimble/modbluetooth_nimble.h @@ -43,15 +43,27 @@ typedef struct _mp_bluetooth_nimble_root_pointers_t { enum { MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF, MP_BLUETOOTH_NIMBLE_BLE_STATE_STARTING, + MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC, MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE, MP_BLUETOOTH_NIMBLE_BLE_STATE_STOPPING, }; extern volatile int mp_bluetooth_nimble_ble_state; -void mp_bluetooth_nimble_port_preinit(void); -void mp_bluetooth_nimble_port_postinit(void); -void mp_bluetooth_nimble_port_deinit(void); +// --- Optionally provided by the MicroPython port. --------------------------- +// (default implementations provided by modbluetooth_nimble.c) + +// Tell the port to init the UART and start the HCI controller. +void mp_bluetooth_nimble_port_hci_init(void); + +// Tell the port to deinit the UART and shutdown the HCI controller. +void mp_bluetooth_nimble_port_hci_deinit(void); + +// Tell the port to run its background task (i.e. poll the UART and pump events). void mp_bluetooth_nimble_port_start(void); +// Tell the port to stop its background task. +void mp_bluetooth_nimble_port_shutdown(void); + + #endif // MICROPY_INCLUDED_EXTMOD_NIMBLE_MODBLUETOOTH_NIMBLE_H diff --git a/extmod/nimble/nimble.mk b/extmod/nimble/nimble.mk index 4e1642c98a..f8a68bc6f5 100644 --- a/extmod/nimble/nimble.mk +++ b/extmod/nimble/nimble.mk @@ -2,16 +2,19 @@ ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) -EXTMOD_SRC_C += extmod/nimble/modbluetooth_nimble.c +EXTMOD_DIR = extmod +NIMBLE_EXTMOD_DIR = $(EXTMOD_DIR)/nimble + +EXTMOD_SRC_C += $(NIMBLE_EXTMOD_DIR)/modbluetooth_nimble.c CFLAGS_MOD += -DMICROPY_BLUETOOTH_NIMBLE=1 -NIMBLE_EXTMOD_DIR = extmod/nimble - # Use NimBLE from the submodule in lib/mynewt-nimble by default, # allowing a port to use their own system version (e.g. ESP32). MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY ?= 0 +CFLAGS_MOD += -DMICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY=$(MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY) + ifeq ($(MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY),0) NIMBLE_LIB_DIR = lib/mynewt-nimble @@ -82,7 +85,7 @@ LIB_SRC_C += $(addprefix $(NIMBLE_LIB_DIR)/, \ ) EXTMOD_SRC_C += $(addprefix $(NIMBLE_EXTMOD_DIR)/, \ - nimble/npl_os.c \ + nimble/nimble_npl_os.c \ hal/hal_uart.c \ ) @@ -98,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 +$(BUILD)/$(NIMBLE_LIB_DIR)/%.o: CFLAGS += -Wno-maybe-uninitialized -Wno-pointer-arith -Wno-unused-but-set-variable -Wno-format -Wno-sign-compare endif diff --git a/extmod/nimble/nimble/npl_os.c b/extmod/nimble/nimble/nimble_npl_os.c similarity index 85% rename from extmod/nimble/nimble/npl_os.c rename to extmod/nimble/nimble/nimble_npl_os.c index 20c2604054..ba3031a8ff 100644 --- a/extmod/nimble/nimble/npl_os.c +++ b/extmod/nimble/nimble/nimble_npl_os.c @@ -29,16 +29,18 @@ #include "py/runtime.h" #include "nimble/ble.h" #include "nimble/nimble_npl.h" -#include "nimble/nimble_hci_uart.h" +#include "extmod/nimble/hal/hal_uart.h" -#define DEBUG_OS_printf(...) //printf(__VA_ARGS__) -#define DEBUG_MALLOC_printf(...) //printf(__VA_ARGS__) -#define DEBUG_EVENT_printf(...) //printf(__VA_ARGS__) -#define DEBUG_MUTEX_printf(...) //printf(__VA_ARGS__) -#define DEBUG_SEM_printf(...) //printf(__VA_ARGS__) -#define DEBUG_CALLOUT_printf(...) //printf(__VA_ARGS__) -#define DEBUG_TIME_printf(...) //printf(__VA_ARGS__) -#define DEBUG_CRIT_printf(...) //printf(__VA_ARGS__) +#include "extmod/modbluetooth.h" + +#define DEBUG_OS_printf(...) // printf(__VA_ARGS__) +#define DEBUG_MALLOC_printf(...) // printf(__VA_ARGS__) +#define DEBUG_EVENT_printf(...) // printf(__VA_ARGS__) +#define DEBUG_MUTEX_printf(...) // printf(__VA_ARGS__) +#define DEBUG_SEM_printf(...) // printf(__VA_ARGS__) +#define DEBUG_CALLOUT_printf(...) // printf(__VA_ARGS__) +#define DEBUG_TIME_printf(...) // printf(__VA_ARGS__) +#define DEBUG_CRIT_printf(...) // printf(__VA_ARGS__) bool ble_npl_os_started(void) { DEBUG_OS_printf("ble_npl_os_started\n"); @@ -138,7 +140,7 @@ int nimble_sprintf(char *str, const char *fmt, ...) { struct ble_npl_eventq *global_eventq = NULL; -void os_eventq_run_all(void) { +void mp_bluetooth_nimble_os_eventq_run_all(void) { for (struct ble_npl_eventq *evq = global_eventq; evq != NULL; evq = evq->nextq) { while (evq->head != NULL) { struct ble_npl_event *ev = evq->head; @@ -238,23 +240,39 @@ 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. + if (sem->count == 0) { uint32_t t0 = mp_hal_ticks_ms(); while (sem->count == 0 && mp_hal_ticks_ms() - t0 < timeout) { - // This function may be called at thread-level, so execute - // mp_bluetooth_nimble_hci_uart_process at raised priority. + // 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; } - __WFI(); + + // Because we're polling, might as well wait for a UART IRQ indicating + // more data available. + mp_bluetooth_nimble_hci_uart_wfi(); } + if (sem->count == 0) { - printf("timeout\n"); + printf("NimBLE: HCI ACK timeout\n"); return BLE_NPL_TIMEOUT; } + DEBUG_SEM_printf("got response in %u ms\n", (int)(mp_hal_ticks_ms() - t0)); } sem->count -= 1; @@ -277,7 +295,7 @@ uint16_t ble_npl_sem_get_count(struct ble_npl_sem *sem) { static struct ble_npl_callout *global_callout = NULL; -void os_callout_process(void) { +void mp_bluetooth_nimble_os_callout_process(void) { uint32_t tnow = mp_hal_ticks_ms(); for (struct ble_npl_callout *c = global_callout; c != NULL; c = c->nextc) { if (!c->active) { @@ -386,12 +404,24 @@ 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? + uint32_t ble_npl_hw_enter_critical(void) { DEBUG_CRIT_printf("ble_npl_hw_enter_critical()\n"); - return raise_irq_pri(15); + return mp_bluetooth_nimble_hci_uart_enter_critical(); } void ble_npl_hw_exit_critical(uint32_t ctx) { DEBUG_CRIT_printf("ble_npl_hw_exit_critical(%u)\n", (uint)ctx); - restore_irq_pri(ctx); + mp_bluetooth_nimble_hci_uart_exit_critical(ctx); } diff --git a/extmod/nimble/nimble/nimble_npl_os.h b/extmod/nimble/nimble/nimble_npl_os.h index 3d886fedb5..5542520661 100644 --- a/extmod/nimble/nimble/nimble_npl_os.h +++ b/extmod/nimble/nimble/nimble_npl_os.h @@ -24,11 +24,15 @@ * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NPL_OS_H -#define MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NPL_OS_H +#ifndef MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NIMBLE_NPL_OS_H +#define MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NIMBLE_NPL_OS_H + +// This is included by nimble/nimble_npl.h -- include that rather than this file directly. #include +// --- Configuration of NimBLE data structures -------------------------------- + #define BLE_NPL_OS_ALIGNMENT (4) #define BLE_NPL_TIME_FOREVER (0xffffffff) @@ -63,4 +67,15 @@ struct ble_npl_sem { volatile uint16_t count; }; +// --- Called by the MicroPython port ----------------------------------------- + +void mp_bluetooth_nimble_os_eventq_run_all(void); +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/extmod/nimble/syscfg/syscfg.h b/extmod/nimble/syscfg/syscfg.h index 6ac420f35e..8a6f2338be 100644 --- a/extmod/nimble/syscfg/syscfg.h +++ b/extmod/nimble/syscfg/syscfg.h @@ -2,12 +2,14 @@ #define MICROPY_INCLUDED_EXTMOD_NIMBLE_SYSCFG_H #include "py/mphal.h" -#include "uart.h" + +#include "mpnimbleport.h" void *nimble_malloc(size_t size); void nimble_free(void *ptr); void *nimble_realloc(void *ptr, size_t size); +// Redirect NimBLE malloc to the GC heap. #define malloc(size) nimble_malloc(size) #define free(ptr) nimble_free(ptr) #define realloc(ptr, size) nimble_realloc(ptr, size) @@ -88,6 +90,7 @@ int nimble_sprintf(char *str, const char *fmt, ...); #define MYNEWT_VAL_BLE_GATT_WRITE_NO_RSP (MYNEWT_VAL_BLE_ROLE_CENTRAL) #define MYNEWT_VAL_BLE_GATT_WRITE_RELIABLE (MYNEWT_VAL_BLE_ROLE_CENTRAL) #define MYNEWT_VAL_BLE_HOST (1) +#define MYNEWT_VAL_BLE_HS_AUTO_START (1) #define MYNEWT_VAL_BLE_HS_DEBUG (0) #define MYNEWT_VAL_BLE_HS_FLOW_CTRL (0) #define MYNEWT_VAL_BLE_HS_FLOW_CTRL_ITVL (1000) diff --git a/ports/esp32/Makefile b/ports/esp32/Makefile index 2cbe9f6be5..dcf4110cfc 100644 --- a/ports/esp32/Makefile +++ b/ports/esp32/Makefile @@ -341,7 +341,7 @@ SRC_C = \ modnetwork.c \ network_lan.c \ network_ppp.c \ - nimble.c \ + mpnimbleport.c \ modsocket.c \ modesp.c \ esp32_partition.c \ diff --git a/ports/esp32/nimble.c b/ports/esp32/mpnimbleport.c similarity index 66% rename from ports/esp32/nimble.c rename to ports/esp32/mpnimbleport.c index 16829732c3..a58fcbdbf4 100644 --- a/ports/esp32/nimble.c +++ b/ports/esp32/mpnimbleport.c @@ -30,28 +30,48 @@ #if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE +#define DEBUG_printf(...) // printf("nimble (esp32): " __VA_ARGS__) + #include "esp_nimble_hci.h" #include "nimble/nimble_port.h" #include "nimble/nimble_port_freertos.h" +#include "extmod/nimble/modbluetooth_nimble.h" + STATIC void ble_host_task(void *param) { + DEBUG_printf("ble_host_task\n"); nimble_port_run(); // This function will return only when nimble_port_stop() is executed. nimble_port_freertos_deinit(); } -void mp_bluetooth_nimble_port_preinit(void) { +void mp_bluetooth_nimble_port_hci_init(void) { + DEBUG_printf("mp_bluetooth_nimble_port_hci_init\n"); esp_nimble_hci_and_controller_init(); } -void mp_bluetooth_nimble_port_postinit(void) { -} +void mp_bluetooth_nimble_port_hci_deinit(void) { + DEBUG_printf("mp_bluetooth_nimble_port_hci_deinit\n"); -void mp_bluetooth_nimble_port_deinit(void) { - nimble_port_stop(); + esp_nimble_hci_and_controller_deinit(); } void mp_bluetooth_nimble_port_start(void) { + DEBUG_printf("mp_bluetooth_nimble_port_start\n"); nimble_port_freertos_init(ble_host_task); } +void mp_bluetooth_nimble_port_shutdown(void) { + DEBUG_printf("mp_bluetooth_nimble_port_shutdown\n"); + + // Despite the name, these is an ESP32-specific (no other NimBLE ports have these functions). + // Calls ble_hs_stop() and waits for stack shutdown. + nimble_port_stop(); + + // Shuts down the event queue. + nimble_port_deinit(); + + // Mark stack as shutdown. + mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF; +} + #endif diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index e7d2e2abc2..7b68299e04 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -480,7 +480,7 @@ endif ifeq ($(MICROPY_PY_BLUETOOTH),1) -SRC_C += modbluetooth_hci.c +SRC_C += mpbthciport.c ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) @@ -490,12 +490,13 @@ endif ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) include $(TOP)/extmod/nimble/nimble.mk -SRC_C += nimble.c +SRC_C += mpnimbleport.c endif ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) +MICROPY_BLUETOOTH_BTSTACK_H4 ?= 1 include $(TOP)/extmod/btstack/btstack.mk -SRC_C += btstack.c +SRC_C += mpbtstackport.c endif ifeq ($(MICROPY_PY_NETWORK_CYW43),1) diff --git a/ports/stm32/modbluetooth_hci.c b/ports/stm32/mpbthciport.c similarity index 75% rename from ports/stm32/modbluetooth_hci.c rename to ports/stm32/mpbthciport.c index 54c60adc01..71a9e4fc7f 100644 --- a/ports/stm32/modbluetooth_hci.c +++ b/ports/stm32/mpbthciport.c @@ -26,17 +26,20 @@ #include "py/runtime.h" #include "py/mphal.h" -#include "extmod/modbluetooth_hci.h" +#include "extmod/mpbthci.h" #include "systick.h" #include "pendsv.h" +#include "lib/utils/mpirq.h" #include "py/obj.h" #if MICROPY_PY_BLUETOOTH +#define DEBUG_printf(...) // printf(__VA_ARGS__) + uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; -// Must be provided by the stack bindings. +// Must be provided by the stack bindings (e.g. mpnimbleport.c or mpbtstackport.c). extern void mp_bluetooth_hci_poll(void); // Hook for pendsv poller to run this periodically every 128ms @@ -61,34 +64,19 @@ STATIC uint16_t hci_uart_rx_buf_cur; STATIC uint16_t hci_uart_rx_buf_len; STATIC uint8_t hci_uart_rx_buf_data[256]; -int mp_bluetooth_hci_controller_deactivate(void) { - return 0; -} - -int mp_bluetooth_hci_controller_sleep_maybe(void) { - return 0; -} - -bool mp_bluetooth_hci_controller_woken(void) { - return true; -} - -int mp_bluetooth_hci_controller_wakeup(void) { - return 0; -} - -int mp_bluetooth_hci_uart_init(uint32_t port) { +int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { (void)port; - return 0; -} - -int mp_bluetooth_hci_uart_activate(void) { + (void)baudrate; rfcore_ble_init(); hci_uart_rx_buf_cur = 0; hci_uart_rx_buf_len = 0; return 0; } +int mp_bluetooth_hci_uart_deinit(void) { + return 0; +} + int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate) { (void)baudrate; return 0; @@ -140,13 +128,16 @@ mp_irq_obj_t mp_bluetooth_hci_uart_irq_obj; static uint8_t hci_uart_rxbuf[512]; mp_obj_t mp_uart_interrupt(mp_obj_t self_in) { - // New HCI data, schedule mp_bluetooth_hci_poll to make the stack handle it. + // 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); return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_1(mp_uart_interrupt_obj, mp_uart_interrupt); -int mp_bluetooth_hci_uart_init(uint32_t port) { +int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { + DEBUG_printf("mp_bluetooth_hci_uart_init (stm32)\n"); + // 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; @@ -154,16 +145,29 @@ int mp_bluetooth_hci_uart_init(uint32_t port) { mp_bluetooth_hci_uart_obj.timeout = 2; mp_bluetooth_hci_uart_obj.timeout_char = 2; 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. + mp_bluetooth_hci_uart_set_baudrate(baudrate); + + return 0; +} + +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; } int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate) { + DEBUG_printf("mp_bluetooth_hci_uart_set_baudrate(%lu) (stm32)\n", baudrate); + if (!baudrate) { + return -1; + } + uart_init(&mp_bluetooth_hci_uart_obj, baudrate, UART_WORDLENGTH_8B, UART_PARITY_NONE, UART_STOPBITS_1, UART_HWCONTROL_RTS | UART_HWCONTROL_CTS); uart_set_rxbuf(&mp_bluetooth_hci_uart_obj, sizeof(hci_uart_rxbuf), hci_uart_rxbuf); - return 0; -} -int mp_bluetooth_hci_uart_activate(void) { // Add IRQ handler for IDLE (i.e. packet finished). uart_irq_config(&mp_bluetooth_hci_uart_obj, false); mp_irq_init(&mp_bluetooth_hci_uart_irq_obj, &uart_irq_methods, MP_OBJ_FROM_PTR(&mp_bluetooth_hci_uart_obj)); @@ -173,13 +177,12 @@ int mp_bluetooth_hci_uart_activate(void) { mp_bluetooth_hci_uart_irq_obj.ishard = true; uart_irq_config(&mp_bluetooth_hci_uart_obj, true); - mp_bluetooth_hci_controller_init(); - mp_bluetooth_hci_controller_activate(); - return 0; } int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len) { + // DEBUG_printf("mp_bluetooth_hci_uart_write (stm32)\n"); + mp_bluetooth_hci_controller_wakeup(); uart_tx_strn(&mp_bluetooth_hci_uart_obj, (void *)buf, len); return 0; @@ -188,7 +191,10 @@ int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len) { // This function expects the controller to be in the wake state via a previous call // to mp_bluetooth_hci_controller_woken. int mp_bluetooth_hci_uart_readchar(void) { + // DEBUG_printf("mp_bluetooth_hci_uart_readchar (stm32)\n"); + if (uart_rx_any(&mp_bluetooth_hci_uart_obj)) { + // DEBUG_printf("... available\n"); return uart_rx_char(&mp_bluetooth_hci_uart_obj); } else { return -1; @@ -197,4 +203,33 @@ int mp_bluetooth_hci_uart_readchar(void) { #endif // defined(STM32WB) +// Default (weak) implementation of the HCI controller interface. +// A driver (e.g. cywbt43.c) can override these for controller-specific +// functionality (i.e. power management). + +MP_WEAK int mp_bluetooth_hci_controller_init(void) { + DEBUG_printf("mp_bluetooth_hci_controller_init (default)\n"); + return 0; +} + +MP_WEAK int mp_bluetooth_hci_controller_deinit(void) { + DEBUG_printf("mp_bluetooth_hci_controller_deinit (default)\n"); + return 0; +} + +MP_WEAK int mp_bluetooth_hci_controller_sleep_maybe(void) { + DEBUG_printf("mp_bluetooth_hci_controller_sleep_maybe (default)\n"); + return 0; +} + +MP_WEAK bool mp_bluetooth_hci_controller_woken(void) { + DEBUG_printf("mp_bluetooth_hci_controller_woken (default)\n"); + return true; +} + +MP_WEAK int mp_bluetooth_hci_controller_wakeup(void) { + DEBUG_printf("mp_bluetooth_hci_controller_wakeup (default)\n"); + return 0; +} + #endif // MICROPY_PY_BLUETOOTH diff --git a/ports/stm32/mpbtstackport.c b/ports/stm32/mpbtstackport.c new file mode 100644 index 0000000000..a031a6a151 --- /dev/null +++ b/ports/stm32/mpbtstackport.c @@ -0,0 +1,107 @@ +/* + * 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. + */ + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK + +#include "lib/btstack/src/btstack.h" +#include "lib/btstack/platform/embedded/btstack_run_loop_embedded.h" +#include "lib/btstack/platform/embedded/hal_cpu.h" +#include "lib/btstack/platform/embedded/hal_time_ms.h" + +#include "extmod/mpbthci.h" +#include "extmod/btstack/btstack_hci_uart.h" +#include "extmod/btstack/modbluetooth_btstack.h" + +// The IRQ functionality in btstack_run_loop_embedded.c is not used, so the +// following three functions are empty. + +void hal_cpu_disable_irqs(void) { +} + +void hal_cpu_enable_irqs(void) { +} + +void hal_cpu_enable_irqs_and_sleep(void) { +} + +uint32_t hal_time_ms(void) { + return mp_hal_ticks_ms(); +} + +STATIC const hci_transport_config_uart_t hci_transport_config_uart = { + HCI_TRANSPORT_CONFIG_UART, + MICROPY_HW_BLE_UART_BAUDRATE, + 3000000, + 0, + NULL, +}; + +void mp_bluetooth_hci_poll(void) { + if (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_OFF) { + return; + } + + // Process uart data. + if (mp_bluetooth_btstack_state != MP_BLUETOOTH_BTSTACK_STATE_HALTING) { + mp_bluetooth_btstack_hci_uart_process(); + } + + // Call the BTstack run loop. + btstack_run_loop_embedded_execute_once(); +} + +void mp_bluetooth_btstack_port_init(void) { + static bool run_loop_init = false; + if (!run_loop_init) { + run_loop_init = true; + btstack_run_loop_init(btstack_run_loop_embedded_get_instance()); + } else { + btstack_run_loop_embedded_get_instance()->init(); + } + + // hci_dump_open(NULL, HCI_DUMP_STDOUT); + const hci_transport_t *transport = hci_transport_h4_instance(&mp_bluetooth_btstack_hci_uart_block); + hci_init(transport, &hci_transport_config_uart); + + // TODO: Probably not necessary for BCM (we have our own firmware loader in the cyw43 driver), + // but might be worth investigating for other controllers in the future. + // hci_set_chipset(btstack_chipset_bcm_instance()); +} + +void mp_bluetooth_btstack_port_deinit(void) { + hci_power_control(HCI_POWER_OFF); + hci_close(); +} + +void mp_bluetooth_btstack_port_start(void) { + hci_power_control(HCI_POWER_ON); +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK diff --git a/ports/stm32/mpbtstackport.h b/ports/stm32/mpbtstackport.h new file mode 100644 index 0000000000..110f921d9a --- /dev/null +++ b/ports/stm32/mpbtstackport.h @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#ifndef MICROPY_INCLUDED_STM32_BTSTACK_PORT_H +#define MICROPY_INCLUDED_STM32_BTSTACK_PORT_H + +// To allow MICROPY_HW_BLE_UART_ID to be resolved. + +#include "uart.h" + +#endif // MICROPY_INCLUDED_STM32_BTSTACK_PORT_H diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 14a0589895..e1e1491d64 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -298,6 +298,7 @@ extern const struct _mp_obj_module_t mp_module_onewire; #if MICROPY_BLUETOOTH_NIMBLE struct _mp_bluetooth_nimble_root_pointers_t; +struct _mp_bluetooth_nimble_malloc_t; #define MICROPY_PORT_ROOT_POINTER_BLUETOOTH_NIMBLE void **bluetooth_nimble_memory; struct _mp_bluetooth_nimble_root_pointers_t *bluetooth_nimble_root_pointers; #else #define MICROPY_PORT_ROOT_POINTER_BLUETOOTH_NIMBLE @@ -410,14 +411,20 @@ static inline mp_uint_t disable_irq(void) { #define MICROPY_THREAD_YIELD() #endif -// The LwIP interface must run at a raised IRQ priority -#define MICROPY_PY_LWIP_ENTER uint32_t atomic_state = raise_irq_pri(IRQ_PRI_PENDSV); -#define MICROPY_PY_LWIP_REENTER atomic_state = raise_irq_pri(IRQ_PRI_PENDSV); -#define MICROPY_PY_LWIP_EXIT restore_irq_pri(atomic_state); +// For regular code that wants to prevent "background tasks" from running. +// These background tasks (LWIP, Bluetooth) run in PENDSV context. +#define MICROPY_PY_PENDSV_ENTER uint32_t atomic_state = raise_irq_pri(IRQ_PRI_PENDSV); +#define MICROPY_PY_PENDSV_REENTER atomic_state = raise_irq_pri(IRQ_PRI_PENDSV); +#define MICROPY_PY_PENDSV_EXIT restore_irq_pri(atomic_state); -// Bluetooth calls must run at a raised IRQ priority -#define MICROPY_PY_BLUETOOTH_ENTER MICROPY_PY_LWIP_ENTER -#define MICROPY_PY_BLUETOOTH_EXIT MICROPY_PY_LWIP_EXIT +// Prevent the "LWIP task" from running. +#define MICROPY_PY_LWIP_ENTER MICROPY_PY_PENDSV_ENTER +#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). +#define MICROPY_PY_BLUETOOTH_ENTER MICROPY_PY_PENDSV_ENTER +#define MICROPY_PY_BLUETOOTH_EXIT MICROPY_PY_PENDSV_EXIT // We need an implementation of the log2 function which is not a macro #define MP_NEED_LOG2 (1) diff --git a/ports/stm32/nimble.c b/ports/stm32/mpnimbleport.c similarity index 56% rename from ports/stm32/nimble.c rename to ports/stm32/mpnimbleport.c index 0d349585fb..1d7c095139 100644 --- a/ports/stm32/nimble.c +++ b/ports/stm32/mpnimbleport.c @@ -31,58 +31,41 @@ #if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE -#include "transport/uart/ble_hci_uart.h" #include "host/ble_hs.h" +#include "nimble/nimble_npl.h" -#include "extmod/modbluetooth_hci.h" +#include "extmod/mpbthci.h" #include "extmod/nimble/modbluetooth_nimble.h" -#include "extmod/nimble/nimble/nimble_hci_uart.h" - -extern void os_eventq_run_all(void); -extern void os_callout_process(void); +#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. void mp_bluetooth_hci_poll(void) { - if (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { - return; - } + 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(); - mp_bluetooth_nimble_hci_uart_process(); - os_callout_process(); - os_eventq_run_all(); -} - -void mp_bluetooth_nimble_port_preinit(void) { - MP_STATE_PORT(bluetooth_nimble_memory) = NULL; - ble_hci_uart_init(); -} - -void mp_bluetooth_nimble_port_postinit(void) { -} - -void mp_bluetooth_nimble_port_deinit(void) { - mp_bluetooth_hci_controller_deactivate(); -} - -void mp_bluetooth_nimble_port_start(void) { - ble_hs_start(); -} - -void mp_bluetooth_nimble_hci_uart_rx(hal_uart_rx_cb_t rx_cb, void *rx_arg) { - bool host_wake = mp_bluetooth_hci_controller_woken(); - - int chr; - while ((chr = mp_bluetooth_hci_uart_readchar()) >= 0) { - // printf("UART RX: %02x\n", data); - rx_cb(rx_arg, chr); - } - - if (host_wake) { - mp_bluetooth_hci_controller_sleep_maybe(); + // Run pending background operations and events, but only after HCI sync. + mp_bluetooth_nimble_os_callout_process(); + mp_bluetooth_nimble_os_eventq_run_all(); } } -void mp_bluetooth_nimble_hci_uart_tx_strn(const char *str, uint len) { - mp_bluetooth_hci_uart_write((const uint8_t *)str, len); +// --- Port-specific helpers for the generic NimBLE bindings. ----------------- + +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 } #endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE diff --git a/ports/stm32/mpnimbleport.h b/ports/stm32/mpnimbleport.h new file mode 100644 index 0000000000..f3ee597183 --- /dev/null +++ b/ports/stm32/mpnimbleport.h @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#ifndef MICROPY_INCLUDED_STM32_NIMBLE_PORT_H +#define MICROPY_INCLUDED_STM32_NIMBLE_PORT_H + +// To allow MICROPY_HW_BLE_UART_ID to be resolved. + +#include "uart.h" + +#endif // MICROPY_INCLUDED_STM32_NIMBLE_PORT_H diff --git a/ports/unix/Makefile b/ports/unix/Makefile index 3e2fa63a16..a9ba5edfad 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -140,18 +140,19 @@ ifeq ($(HAVE_LIBUSB),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 +# Runs in a thread, cannot make calls into the VM. +CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK=0 MICROPY_BLUETOOTH_BTSTACK ?= 1 MICROPY_BLUETOOTH_BTSTACK_USB ?= 1 ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) GIT_SUBMODULES += lib/btstack - include $(TOP)/extmod/btstack/btstack.mk endif endif + endif ifeq ($(MICROPY_PY_FFI),1) @@ -198,7 +199,9 @@ SRC_C = \ alloc.c \ coverage.c \ fatfs_port.c \ - btstack_usb.c \ + mpbtstackport_common.c \ + mpbtstackport_usb.c \ + mpnimbleport.c \ $(SRC_MOD) \ $(wildcard $(VARIANT_DIR)/*.c) diff --git a/ports/unix/mpbtstackport.h b/ports/unix/mpbtstackport.h new file mode 100644 index 0000000000..29433cc7fb --- /dev/null +++ b/ports/unix/mpbtstackport.h @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#ifndef MICROPY_INCLUDED_UNIX_BTSTACK_PORT_H +#define MICROPY_INCLUDED_UNIX_BTSTACK_PORT_H + +#define MICROPY_HW_BLE_UART_ID (0) +#define MICROPY_HW_BLE_UART_BAUDRATE (1000000) + +bool mp_bluetooth_hci_poll(void); + +#if MICROPY_BLUETOOTH_BTSTACK_USB +void mp_bluetooth_btstack_port_init_usb(void); +#endif + +#endif // MICROPY_INCLUDED_UNIX_BTSTACK_PORT_H diff --git a/ports/unix/mpbtstackport_common.c b/ports/unix/mpbtstackport_common.c new file mode 100644 index 0000000000..402cf95283 --- /dev/null +++ b/ports/unix/mpbtstackport_common.c @@ -0,0 +1,92 @@ +/* + * 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. + */ + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK + +#include "lib/btstack/src/btstack.h" + +#include "lib/btstack/platform/embedded/btstack_run_loop_embedded.h" +#include "lib/btstack/platform/embedded/hal_cpu.h" +#include "lib/btstack/platform/embedded/hal_time_ms.h" + +#include "extmod/btstack/modbluetooth_btstack.h" + +#include "mpbtstackport.h" + +// Called by the UART polling thread in mpbthciport.c, or by the USB polling thread in mpbtstackport_usb.c. +bool mp_bluetooth_hci_poll(void) { + if (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_STARTING || mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_ACTIVE || mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_HALTING) { + // 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(); + btstack_run_loop_embedded_execute_once(); + MICROPY_END_ATOMIC_SECTION(atomic_state); + + return true; + } + + return false; +} + +// The IRQ functionality in btstack_run_loop_embedded.c is not used, so the +// following three functions are empty. + +void hal_cpu_disable_irqs(void) { +} + +void hal_cpu_enable_irqs(void) { +} + +void hal_cpu_enable_irqs_and_sleep(void) { +} + +uint32_t hal_time_ms(void) { + return mp_hal_ticks_ms(); +} + +void mp_bluetooth_btstack_port_init(void) { + static bool run_loop_init = false; + if (!run_loop_init) { + run_loop_init = true; + btstack_run_loop_init(btstack_run_loop_embedded_get_instance()); + } else { + btstack_run_loop_embedded_get_instance()->init(); + } + + // hci_dump_open(NULL, HCI_DUMP_STDOUT); + + #if MICROPY_BLUETOOTH_BTSTACK_USB + mp_bluetooth_btstack_port_init_usb(); + #endif +} + +void mp_hal_get_mac(int idx, uint8_t buf[6]) { +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK diff --git a/ports/unix/btstack_usb.c b/ports/unix/mpbtstackport_usb.c similarity index 54% rename from ports/unix/btstack_usb.c rename to ports/unix/mpbtstackport_usb.c index ab6a49f39a..28d2c8c543 100644 --- a/ports/unix/btstack_usb.c +++ b/ports/unix/mpbtstackport_usb.c @@ -31,7 +31,7 @@ #include "py/mperrno.h" #include "py/mphal.h" -#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_USB #include "lib/btstack/src/btstack.h" #include "lib/btstack/platform/embedded/btstack_run_loop_embedded.h" @@ -40,76 +40,15 @@ #include "extmod/btstack/modbluetooth_btstack.h" +#include "mpbtstackport.h" + #if !MICROPY_PY_THREAD #error Unix btstack requires MICROPY_PY_THREAD #endif STATIC const useconds_t USB_POLL_INTERVAL_US = 1000; -STATIC const uint8_t read_static_address_command_complete_prefix[] = { 0x0e, 0x1b, 0x01, 0x09, 0xfc }; - -STATIC uint8_t local_addr[6] = {0}; -STATIC uint8_t static_address[6] = {0}; -STATIC volatile bool have_addr = false; -STATIC bool using_static_address = false; - -STATIC btstack_packet_callback_registration_t hci_event_callback_registration; - -STATIC void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { - (void)channel; - (void)size; - if (packet_type != HCI_EVENT_PACKET) { - return; - } - switch (hci_event_packet_get_type(packet)) { - case BTSTACK_EVENT_STATE: - if (btstack_event_state_get_state(packet) != HCI_STATE_WORKING) { - return; - } - gap_local_bd_addr(local_addr); - if (using_static_address) { - memcpy(local_addr, static_address, sizeof(local_addr)); - } - have_addr = true; - break; - case HCI_EVENT_COMMAND_COMPLETE: - if (memcmp(packet, read_static_address_command_complete_prefix, sizeof(read_static_address_command_complete_prefix)) == 0) { - reverse_48(&packet[7], static_address); - gap_random_address_set(static_address); - using_static_address = true; - have_addr = true; - } - break; - default: - break; - } -} - -// The IRQ functionality in btstack_run_loop_embedded.c is not used, so the -// following three functions are empty. - -void hal_cpu_disable_irqs(void) { -} - -void hal_cpu_enable_irqs(void) { -} - -void hal_cpu_enable_irqs_and_sleep(void) { -} - -uint32_t hal_time_ms(void) { - return mp_hal_ticks_ms(); -} - -void mp_bluetooth_btstack_port_init(void) { - static bool run_loop_init = false; - if (!run_loop_init) { - run_loop_init = true; - btstack_run_loop_init(btstack_run_loop_embedded_get_instance()); - } else { - btstack_run_loop_embedded_get_instance()->init(); - } - +void mp_bluetooth_btstack_port_init_usb(void) { // MICROPYBTUSB can be a ':'' or '-' separated port list. char *path = getenv("MICROPYBTUSB"); if (path != NULL) { @@ -128,11 +67,7 @@ void mp_bluetooth_btstack_port_init(void) { hci_transport_usb_set_path(usb_path_len, usb_path); } - // hci_dump_open(NULL, HCI_DUMP_STDOUT); hci_init(hci_transport_usb_instance(), NULL); - - hci_event_callback_registration.callback = &packet_handler; - hci_add_event_handler(&hci_event_callback_registration); } STATIC pthread_t bstack_thread_id; @@ -142,9 +77,12 @@ void mp_bluetooth_btstack_port_deinit(void) { // Wait for the poll loop to terminate when the state is set to OFF. pthread_join(bstack_thread_id, NULL); - have_addr = false; } + +// Provided by mpbstackport_common.c. +extern bool mp_bluetooth_hci_poll(void); + STATIC void *btstack_thread(void *arg) { (void)arg; hci_power_control(HCI_POWER_ON); @@ -155,19 +93,15 @@ STATIC void *btstack_thread(void *arg) { // in modbluetooth_btstack.c setting the state back to OFF. // Or, if a timeout results in it being set to TIMEOUT. - while (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_STARTING || mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_ACTIVE) { - // 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(); - btstack_run_loop_embedded_execute_once(); - MICROPY_END_ATOMIC_SECTION(atomic_state); + while (true) { + if (!mp_bluetooth_hci_poll()) { + break; + } // The USB transport schedules events to the run loop at 1ms intervals, // and the implementation currently polls rather than selects. usleep(USB_POLL_INTERVAL_US); } - - hci_close(); - return NULL; } @@ -179,13 +113,4 @@ void mp_bluetooth_btstack_port_start(void) { pthread_create(&bstack_thread_id, &attr, &btstack_thread, NULL); } -void mp_hal_get_mac(int idx, uint8_t buf[6]) { - if (idx == MP_HAL_MAC_BDADDR) { - if (!have_addr) { - mp_raise_OSError(MP_ENODEV); - } - memcpy(buf, local_addr, sizeof(local_addr)); - } -} - -#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_USB diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index 08605842fd..9e24ab10cd 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -309,9 +309,11 @@ void mp_unix_mark_exec(void); #define MP_STATE_PORT MP_STATE_VM -#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK +#if MICROPY_PY_BLUETOOTH +#if MICROPY_BLUETOOTH_BTSTACK struct _mp_bluetooth_btstack_root_pointers_t; #define MICROPY_BLUETOOTH_ROOT_POINTERS struct _mp_bluetooth_btstack_root_pointers_t *bluetooth_btstack_root_pointers; +#endif #else #define MICROPY_BLUETOOTH_ROOT_POINTERS #endif From 5b08676d6a644487c4ec6f63aef02888f8da8b19 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Mon, 17 Aug 2020 18:12:20 +1000 Subject: [PATCH 009/337] extmod/nimble: Set struct alignment correctly on 64-bit arch. --- extmod/nimble/nimble/nimble_npl_os.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extmod/nimble/nimble/nimble_npl_os.h b/extmod/nimble/nimble/nimble_npl_os.h index 5542520661..bfa35d0952 100644 --- a/extmod/nimble/nimble/nimble_npl_os.h +++ b/extmod/nimble/nimble/nimble_npl_os.h @@ -33,7 +33,7 @@ // --- Configuration of NimBLE data structures -------------------------------- -#define BLE_NPL_OS_ALIGNMENT (4) +#define BLE_NPL_OS_ALIGNMENT (sizeof(uintptr_t)) #define BLE_NPL_TIME_FOREVER (0xffffffff) typedef uint32_t ble_npl_time_t; From f3f31ac9599fcf1c94dca20046da69773cc3418a Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 14 Aug 2020 16:28:55 +1000 Subject: [PATCH 010/337] extmod/nimble: Make nimble_malloc work with allocated size. --- extmod/nimble/nimble/nimble_npl_os.c | 104 ++++++++++++++++++--------- ports/stm32/mpconfigport.h | 2 +- 2 files changed, 72 insertions(+), 34 deletions(-) diff --git a/extmod/nimble/nimble/nimble_npl_os.c b/extmod/nimble/nimble/nimble_npl_os.c index ba3031a8ff..500f7c0a51 100644 --- a/extmod/nimble/nimble/nimble_npl_os.c +++ b/extmod/nimble/nimble/nimble_npl_os.c @@ -58,44 +58,58 @@ void *ble_npl_get_current_task_id(void) { // Maintain a linked list of heap memory that we've passed to Nimble, // discoverable via the bluetooth_nimble_memory root pointer. -// MP_STATE_PORT(bluetooth_nimble_memory) is a pointer to [next, prev, data...]. +typedef struct _mp_bluetooth_nimble_malloc_t { + struct _mp_bluetooth_nimble_malloc_t *prev; + struct _mp_bluetooth_nimble_malloc_t *next; + size_t size; + uint8_t data[]; +} mp_bluetooth_nimble_malloc_t; // TODO: This is duplicated from mbedtls. Perhaps make this a generic feature? -void *m_malloc_bluetooth(size_t size) { - void **ptr = m_malloc0(size + 2 * sizeof(uintptr_t)); - if (MP_STATE_PORT(bluetooth_nimble_memory) != NULL) { - MP_STATE_PORT(bluetooth_nimble_memory)[0] = ptr; +STATIC void *m_malloc_bluetooth(size_t size) { + size += sizeof(mp_bluetooth_nimble_malloc_t); + mp_bluetooth_nimble_malloc_t *alloc = m_malloc0(size); + alloc->size = size; + alloc->next = MP_STATE_PORT(bluetooth_nimble_memory); + if (alloc->next) { + alloc->next->prev = alloc; } - ptr[0] = NULL; - ptr[1] = MP_STATE_PORT(bluetooth_nimble_memory); - MP_STATE_PORT(bluetooth_nimble_memory) = ptr; - return &ptr[2]; + MP_STATE_PORT(bluetooth_nimble_memory) = alloc; + return alloc->data; } -void m_free_bluetooth(void *ptr_in) { - void **ptr = &((void**)ptr_in)[-2]; - if (ptr[1] != NULL) { - ((void**)ptr[1])[0] = ptr[0]; +STATIC mp_bluetooth_nimble_malloc_t* get_nimble_malloc(void *ptr) { + return (mp_bluetooth_nimble_malloc_t*)((uintptr_t)ptr - sizeof(mp_bluetooth_nimble_malloc_t)); +} + +STATIC void m_free_bluetooth(void *ptr) { + mp_bluetooth_nimble_malloc_t *alloc = get_nimble_malloc(ptr); + if (alloc->next) { + alloc->next->prev = alloc->prev; } - if (ptr[0] != NULL) { - ((void**)ptr[0])[1] = ptr[1]; + if (alloc->prev) { + alloc->prev->next = alloc->next; } else { - MP_STATE_PORT(bluetooth_nimble_memory) = ptr[1]; + MP_STATE_PORT(bluetooth_nimble_memory) = NULL; } - m_free(ptr); + m_free(alloc + #if MICROPY_MALLOC_USES_ALLOCATED_SIZE + , alloc->size + #endif + ); } // Check if a nimble ptr is tracked. // If it isn't, that means that it's from a previous soft-reset cycle. STATIC bool is_valid_nimble_malloc(void *ptr) { DEBUG_MALLOC_printf("NIMBLE is_valid_nimble_malloc(%p)\n", ptr); - void** search = MP_STATE_PORT(bluetooth_nimble_memory); - while (search) { - if (&search[2] == ptr) { + mp_bluetooth_nimble_malloc_t *alloc = MP_STATE_PORT(bluetooth_nimble_memory); + while (alloc) { + DEBUG_MALLOC_printf("NIMBLE checking: %p\n", alloc->data); + if (alloc->data == ptr) { return true; } - - search = (void**)search[1]; + alloc = alloc->next; } return false; } @@ -110,22 +124,46 @@ void *nimble_malloc(size_t size) { // Only free if it's still a valid pointer. void nimble_free(void *ptr) { DEBUG_MALLOC_printf("NIMBLE free(%p)\n", ptr); - if (ptr && is_valid_nimble_malloc(ptr)) { - m_free_bluetooth(ptr); + + if (ptr) { + // After a stack re-init, NimBLE has variables in BSS that might be + // still pointing to old allocations from a previous init. We can't do + // anything about this (e.g. ble_gatts_free_mem is private). But we + // can identify that this is a non-null, invalid alloc because it + // won't be in our list, so ignore it because it is effectively free'd + // anyway (it's not referenced by anything the GC can find). + if (is_valid_nimble_malloc(ptr)) { + m_free_bluetooth(ptr); + } } } // Only realloc if it's still a valid pointer. Otherwise just malloc. -void *nimble_realloc(void *ptr, size_t size) { - // This is only used by ble_gatts.c to grow the queue of pending services to be registered. - DEBUG_MALLOC_printf("NIMBLE realloc(%p, %u)\n", ptr, (uint)size); - void *ptr2 = nimble_malloc(size); - if (ptr && is_valid_nimble_malloc(ptr)) { - // If it's a realloc and we still have the old data, then copy it. - // This will happen as we add services. - memcpy(ptr2, ptr, size); - m_free_bluetooth(ptr); +void *nimble_realloc(void *ptr, size_t new_size) { + DEBUG_MALLOC_printf("NIMBLE realloc(%p, %u)\n", ptr, (uint)new_size); + + if (!ptr) { + return nimble_malloc(new_size); } + + assert(is_valid_nimble_malloc(ptr)); + + // Existing alloc is big enough. + mp_bluetooth_nimble_malloc_t *alloc = get_nimble_malloc(ptr); + size_t old_size = alloc->size - sizeof(mp_bluetooth_nimble_malloc_t); + if (old_size >= new_size) { + return ptr; + } + + // Allocate a new, larger region. + void *ptr2 = m_malloc_bluetooth(new_size); + + // Copy old, smaller region into new region. + memcpy(ptr2, ptr, old_size); + m_free_bluetooth(ptr); + + DEBUG_MALLOC_printf(" --> %p\n", ptr2); + return ptr2; } diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index e1e1491d64..95b539603f 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -299,7 +299,7 @@ extern const struct _mp_obj_module_t mp_module_onewire; #if MICROPY_BLUETOOTH_NIMBLE struct _mp_bluetooth_nimble_root_pointers_t; struct _mp_bluetooth_nimble_malloc_t; -#define MICROPY_PORT_ROOT_POINTER_BLUETOOTH_NIMBLE void **bluetooth_nimble_memory; struct _mp_bluetooth_nimble_root_pointers_t *bluetooth_nimble_root_pointers; +#define MICROPY_PORT_ROOT_POINTER_BLUETOOTH_NIMBLE struct _mp_bluetooth_nimble_malloc_t *bluetooth_nimble_memory; struct _mp_bluetooth_nimble_root_pointers_t *bluetooth_nimble_root_pointers; #else #define MICROPY_PORT_ROOT_POINTER_BLUETOOTH_NIMBLE #endif From aa18ab7db2a055440cb4798487c12809c74935c5 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 14 Aug 2020 16:29:22 +1000 Subject: [PATCH 011/337] extmod/nimble: Implement NimBLE mutex. --- extmod/nimble/nimble/nimble_npl_os.c | 49 +++++++++++++++++++++++++--- extmod/nimble/nimble/nimble_npl_os.h | 1 + 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/extmod/nimble/nimble/nimble_npl_os.c b/extmod/nimble/nimble/nimble_npl_os.c index 500f7c0a51..46f7a0b291 100644 --- a/extmod/nimble/nimble/nimble_npl_os.c +++ b/extmod/nimble/nimble/nimble_npl_os.c @@ -249,21 +249,62 @@ 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\n", mu, (uint)timeout, (uint)mu->locked); - mu->locked = 1; + DEBUG_MUTEX_printf("ble_npl_mutex_pend(%p, %u) locked=%u irq=%d\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(); + } + + ++mu->locked; + return BLE_NPL_OK; } ble_npl_error_t ble_npl_mutex_release(struct ble_npl_mutex *mu) { - DEBUG_MUTEX_printf("ble_npl_mutex_release(%p) locked=%u\n", mu, (uint)mu->locked); - mu->locked = 0; + DEBUG_MUTEX_printf("ble_npl_mutex_release(%p) locked=%u irq=%d\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; } diff --git a/extmod/nimble/nimble/nimble_npl_os.h b/extmod/nimble/nimble/nimble_npl_os.h index bfa35d0952..3ef07aa9cc 100644 --- a/extmod/nimble/nimble/nimble_npl_os.h +++ b/extmod/nimble/nimble/nimble_npl_os.h @@ -61,6 +61,7 @@ struct ble_npl_callout { struct ble_npl_mutex { volatile uint8_t locked; + volatile uint32_t atomic_state; }; struct ble_npl_sem { From feed69aa5c4e9fda037497a1b63c05a27b164920 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 14 Aug 2020 16:40:55 +1000 Subject: [PATCH 012/337] unix/Makefile: Always enable -f*-sections regardless of DEBUG setting. --- ports/unix/Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ports/unix/Makefile b/ports/unix/Makefile index a9ba5edfad..090b58d8ec 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -47,10 +47,12 @@ ifdef DEBUG COPT ?= -O0 else COPT ?= -Os -COPT += -fdata-sections -ffunction-sections COPT += -DNDEBUG endif +# Remove unused sections. +COPT += -fdata-sections -ffunction-sections + # Always enable symbols -- They're occasionally useful, and don't make it into the # final .bin/.hex/.dfu so the extra size doesn't matter. CFLAGS += -g From 1b1b22905e1aa7676c51078f75b1bd73d8383a4a Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 14 Aug 2020 15:46:53 +1000 Subject: [PATCH 013/337] unix: Implement BLE H4 HCI UART for btstack/nimble. This commit adds support for using Bluetooth on the unix port via a H4 serial interface (distinct from a USB dongle), with both BTstack and NimBLE Bluetooth stacks. Note that MICROPY_PY_BLUETOOTH is now disabled for the coverage variant. Prior to this commit Bluetooth was anyway not being built on Travis because libusb was not detected. But now that bluetooth works in H4 mode it will be built, and will lead to a large decrease in coverage because Bluetooth tests cannot be run on Travis. --- extmod/btstack/btstack.mk | 8 + ports/unix/Makefile | 47 +++- ports/unix/mpbthciport.c | 219 ++++++++++++++++++ ports/unix/mpbtstackport.h | 5 + ports/unix/mpbtstackport_common.c | 7 + ports/unix/mpbtstackport_h4.c | 80 +++++++ ports/unix/mpconfigport.h | 7 +- ports/unix/mpnimbleport.c | 84 +++++++ ports/unix/mpnimbleport.h | 33 +++ .../unix/variants/coverage/mpconfigvariant.mk | 1 - ports/unix/variants/dev/mpconfigvariant.mk | 3 +- 11 files changed, 485 insertions(+), 9 deletions(-) create mode 100644 ports/unix/mpbthciport.c create mode 100644 ports/unix/mpbtstackport_h4.c create mode 100644 ports/unix/mpnimbleport.c create mode 100644 ports/unix/mpnimbleport.h diff --git a/extmod/btstack/btstack.mk b/extmod/btstack/btstack.mk index 3084601b85..7e5d2f646d 100644 --- a/extmod/btstack/btstack.mk +++ b/extmod/btstack/btstack.mk @@ -30,10 +30,18 @@ SRC_BTSTACK = \ $(addprefix lib/btstack/src/ble/, $(filter-out %_tlv.c, $(SRC_BLE_FILES))) \ lib/btstack/platform/embedded/btstack_run_loop_embedded.c +ifeq ($(MICROPY_BLUETOOTH_BTSTACK_USB),1) +ifeq ($(MICROPY_BLUETOOTH_BTSTACK_H4),1) + $(error Cannot specifiy both MICROPY_BLUETOOTH_BTSTACK_USB and MICROPY_BLUETOOTH_BTSTACK_H4) +endif +endif + ifeq ($(MICROPY_BLUETOOTH_BTSTACK_USB),1) SRC_BTSTACK += \ lib/btstack/platform/libusb/hci_transport_h2_libusb.c +CFLAGS_MOD += -DMICROPY_BLUETOOTH_BTSTACK_USB=1 + CFLAGS += $(shell pkg-config libusb-1.0 --cflags) LDFLAGS += $(shell pkg-config libusb-1.0 --libs) endif diff --git a/ports/unix/Makefile b/ports/unix/Makefile index 090b58d8ec..ff5f355022 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -134,24 +134,57 @@ CFLAGS_MOD += -DMICROPY_PY_THREAD=1 -DMICROPY_PY_THREAD_GIL=0 LDFLAGS_MOD += $(LIBPTHREAD) endif -# If the variant enables it and we have libusb, enable BTStack support for USB adaptors. +# If the variant enables it, enable modbluetooth. ifeq ($(MICROPY_PY_BLUETOOTH),1) HAVE_LIBUSB := $(shell (which pkg-config > /dev/null && pkg-config --exists libusb-1.0) 2>/dev/null && echo '1') -ifeq ($(HAVE_LIBUSB),1) + +# Only one stack can be enabled. +ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) +ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) +$(error Cannot enable both NimBLE and BTstack at the same time) +endif +endif + +# Default to btstack, but a variant (or make command line) can set NimBLE +# explicitly (which is always via H4 UART). +ifneq ($(MICROPY_BLUETOOTH_NIMBLE),1) +ifneq ($(MICROPY_BLUETOOTH_BTSTACK),1) +MICROPY_BLUETOOTH_BTSTACK ?= 1 +endif +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 -MICROPY_BLUETOOTH_BTSTACK ?= 1 -MICROPY_BLUETOOTH_BTSTACK_USB ?= 1 - ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) + +# Figure out which BTstack transport to use. +ifeq ($(MICROPY_BLUETOOTH_BTSTACK_H4),1) +ifeq ($(MICROPY_BLUETOOTH_BTSTACK_USB),1) +$(error Cannot enable BTstack support for USB and H4 UART at the same time) +endif +else +ifeq ($(HAVE_LIBUSB),1) +# Default to btstack-over-usb. +MICROPY_BLUETOOTH_BTSTACK_USB ?= 1 +else +# Fallback to HCI controller via a H4 UART (e.g. Zephyr on nRF) over a /dev/tty serial port. +MICROPY_BLUETOOTH_BTSTACK_H4 ?= 1 +endif +endif + +# BTstack is enabled. GIT_SUBMODULES += lib/btstack include $(TOP)/extmod/btstack/btstack.mk -endif + +else + +# NimBLE is enabled. +GIT_SUBMODULES += lib/mynewt-nimble +include $(TOP)/extmod/nimble/nimble.mk endif @@ -201,7 +234,9 @@ SRC_C = \ alloc.c \ coverage.c \ fatfs_port.c \ + mpbthciport.c \ mpbtstackport_common.c \ + mpbtstackport_h4.c \ mpbtstackport_usb.c \ mpnimbleport.c \ $(SRC_MOD) \ diff --git a/ports/unix/mpbthciport.c b/ports/unix/mpbthciport.c new file mode 100644 index 0000000000..5a5c1d4a4e --- /dev/null +++ b/ports/unix/mpbthciport.c @@ -0,0 +1,219 @@ +/* + * 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. + */ + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH && (MICROPY_BLUETOOTH_NIMBLE || (MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_H4)) + +#if !MICROPY_PY_THREAD +#error Unix HCI UART requires MICROPY_PY_THREAD +#endif + +#include "extmod/modbluetooth.h" +#include "extmod/mpbthci.h" + +#include +#include + +#include +#include +#include +#include + +#define DEBUG_printf(...) // printf(__VA_ARGS__) +#define DEBUG_HCI_DUMP (0) + +uint8_t mp_bluetooth_hci_cmd_buf[4 + 256]; + +// 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; + +STATIC int uart_fd = -1; +STATIC pthread_t hci_poll_thread_id; + +STATIC void *hci_poll_thread(void *arg) { + (void)arg; + + // This will return false when the stack is shutdown. + while (mp_bluetooth_hci_poll()) { + usleep(UART_POLL_INTERVAL_US); + } + + return NULL; +} + +STATIC int configure_uart(void) { + struct termios toptions; + + // Get existing config. + if (tcgetattr(uart_fd, &toptions) < 0) { + DEBUG_printf("Couldn't get term attributes"); + return -1; + } + + // Raw mode (disable all processing). + cfmakeraw(&toptions); + + // 8N1, no parity. + toptions.c_cflag &= ~CSTOPB; + toptions.c_cflag |= CS8; + toptions.c_cflag &= ~PARENB; + + // Enable receiver, ignore modem control lines + toptions.c_cflag |= CREAD | CLOCAL; + + // Blocking, single-byte reads. + toptions.c_cc[VMIN] = 1; + toptions.c_cc[VTIME] = 0; + + // Enable HW RTS/CTS flow control. + toptions.c_iflag &= ~(IXON | IXOFF | IXANY); + toptions.c_cflag |= CRTSCTS; + + // 1Mbit (TODO: make this configurable). + speed_t brate = B1000000; + cfsetospeed(&toptions, brate); + cfsetispeed(&toptions, brate); + + // Apply immediately. + if (tcsetattr(uart_fd, TCSANOW, &toptions) < 0) { + DEBUG_printf("Couldn't set term attributes"); + return -1; + } + + return 0; +} + +// HCI UART bindings. +int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { + (void)port; + (void)baudrate; + + DEBUG_printf("mp_bluetooth_hci_uart_init (unix)\n"); + + char uart_device_name[256] = "/dev/ttyUSB0"; + + char *path = getenv("MICROPYBTUART"); + if (path != NULL) { + strcpy(uart_device_name, path); + } + DEBUG_printf("Using HCI UART: %s\n", uart_device_name); + + int flags = O_RDWR | O_NOCTTY | O_NONBLOCK; + uart_fd = open(uart_device_name, flags); + if (uart_fd == -1) { + DEBUG_printf("Unable to open port %s", uart_device_name); + return -1; + } + + if (configure_uart()) { + return -1; + } + + // Create a thread to run the polling loop. + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_create(&hci_poll_thread_id, &attr, &hci_poll_thread, NULL); + + return 0; +} + +int mp_bluetooth_hci_uart_deinit(void) { + DEBUG_printf("mp_bluetooth_hci_uart_deinit\n"); + + // Wait for the poll loop to terminate when the state is set to OFF. + pthread_join(hci_poll_thread_id, NULL); + + // Close the UART. + close(uart_fd); + uart_fd = -1; + + return 0; +} + +int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate) { + (void)baudrate; + DEBUG_printf("mp_bluetooth_hci_uart_set_baudrate\n"); + return 0; +} + +int mp_bluetooth_hci_uart_readchar(void) { + // DEBUG_printf("mp_bluetooth_hci_uart_readchar\n"); + + uint8_t c; + ssize_t bytes_read = read(uart_fd, &c, 1); + + if (bytes_read == 1) { + #if DEBUG_HCI_DUMP + printf("[% 8ld] RX: %02x\n", mp_hal_ticks_ms(), c); + #endif + return c; + } else { + return -1; + } +} + +int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len) { + // DEBUG_printf("mp_bluetooth_hci_uart_write\n"); + + #if DEBUG_HCI_DUMP + printf("[% 8ld] TX: %02x", mp_hal_ticks_ms(), buf[0]); + for (size_t i = 1; i < len; ++i) { + printf(":%02x", buf[i]); + } + printf("\n"); + #endif + + return write(uart_fd, buf, len); +} + +// No-op implementations of HCI controller interface. +int mp_bluetooth_hci_controller_init(void) { + return 0; +} + +int mp_bluetooth_hci_controller_deinit(void) { + return 0; +} + +int mp_bluetooth_hci_controller_sleep_maybe(void) { + return 0; +} + +bool mp_bluetooth_hci_controller_woken(void) { + return true; +} + +int mp_bluetooth_hci_controller_wakeup(void) { + return 0; +} + +#endif // MICROPY_PY_BLUETOOTH && (MICROPY_BLUETOOTH_NIMBLE || (MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_H4)) diff --git a/ports/unix/mpbtstackport.h b/ports/unix/mpbtstackport.h index 29433cc7fb..c82e8bd812 100644 --- a/ports/unix/mpbtstackport.h +++ b/ports/unix/mpbtstackport.h @@ -32,6 +32,11 @@ bool mp_bluetooth_hci_poll(void); +#if MICROPY_BLUETOOTH_BTSTACK_H4 +void mp_bluetooth_hci_poll_h4(void); +void mp_bluetooth_btstack_port_init_h4(void); +#endif + #if MICROPY_BLUETOOTH_BTSTACK_USB void mp_bluetooth_btstack_port_init_usb(void); #endif diff --git a/ports/unix/mpbtstackport_common.c b/ports/unix/mpbtstackport_common.c index 402cf95283..eb1b1c9c80 100644 --- a/ports/unix/mpbtstackport_common.c +++ b/ports/unix/mpbtstackport_common.c @@ -45,6 +45,9 @@ bool mp_bluetooth_hci_poll(void) { if (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_STARTING || mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_ACTIVE || mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_HALTING) { // 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(); + #if MICROPY_BLUETOOTH_BTSTACK_H4 + mp_bluetooth_hci_poll_h4(); + #endif btstack_run_loop_embedded_execute_once(); MICROPY_END_ATOMIC_SECTION(atomic_state); @@ -81,6 +84,10 @@ void mp_bluetooth_btstack_port_init(void) { // hci_dump_open(NULL, HCI_DUMP_STDOUT); + #if MICROPY_BLUETOOTH_BTSTACK_H4 + mp_bluetooth_btstack_port_init_h4(); + #endif + #if MICROPY_BLUETOOTH_BTSTACK_USB mp_bluetooth_btstack_port_init_usb(); #endif diff --git a/ports/unix/mpbtstackport_h4.c b/ports/unix/mpbtstackport_h4.c new file mode 100644 index 0000000000..4fdc20c22b --- /dev/null +++ b/ports/unix/mpbtstackport_h4.c @@ -0,0 +1,80 @@ +/* + * 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. + */ + +#include + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_H4 + +#include "lib/btstack/chipset/zephyr/btstack_chipset_zephyr.h" + +#include "extmod/btstack/btstack_hci_uart.h" +#include "extmod/btstack/modbluetooth_btstack.h" + +#include "mpbtstackport.h" + +#define DEBUG_printf(...) // printf(__VA_ARGS__) + +STATIC hci_transport_config_uart_t hci_transport_config_uart = { + HCI_TRANSPORT_CONFIG_UART, + 1000000, // initial baudrate + 0, // main baudrate + 1, // flow control + NULL, // device name +}; + +void mp_bluetooth_hci_poll_h4(void) { + if (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_STARTING || mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_ACTIVE) { + mp_bluetooth_btstack_hci_uart_process(); + } +} + +void mp_bluetooth_btstack_port_init_h4(void) { + DEBUG_printf("mp_bluetooth_btstack_port_init_h4\n"); + + const hci_transport_t *transport = hci_transport_h4_instance(&mp_bluetooth_btstack_hci_uart_block); + hci_init(transport, &hci_transport_config_uart); + + hci_set_chipset(btstack_chipset_zephyr_instance()); +} + +void mp_bluetooth_btstack_port_deinit(void) { + DEBUG_printf("mp_bluetooth_btstack_port_deinit\n"); + + hci_power_control(HCI_POWER_OFF); + hci_close(); +} + +void mp_bluetooth_btstack_port_start(void) { + DEBUG_printf("mp_bluetooth_btstack_port_start\n"); + + hci_power_control(HCI_POWER_ON); +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_H4 diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index 9e24ab10cd..c74d2fd84a 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -314,6 +314,11 @@ void mp_unix_mark_exec(void); struct _mp_bluetooth_btstack_root_pointers_t; #define MICROPY_BLUETOOTH_ROOT_POINTERS struct _mp_bluetooth_btstack_root_pointers_t *bluetooth_btstack_root_pointers; #endif +#if MICROPY_BLUETOOTH_NIMBLE +struct _mp_bluetooth_nimble_root_pointers_t; +struct _mp_bluetooth_nimble_malloc_t; +#define MICROPY_BLUETOOTH_ROOT_POINTERS struct _mp_bluetooth_nimble_malloc_t *bluetooth_nimble_memory; struct _mp_bluetooth_nimble_root_pointers_t *bluetooth_nimble_root_pointers; +#endif #else #define MICROPY_BLUETOOTH_ROOT_POINTERS #endif @@ -353,7 +358,7 @@ struct _mp_bluetooth_btstack_root_pointers_t; #endif #if MICROPY_PY_THREAD -#define MICROPY_BEGIN_ATOMIC_SECTION() (mp_thread_unix_begin_atomic_section(), 0) +#define MICROPY_BEGIN_ATOMIC_SECTION() (mp_thread_unix_begin_atomic_section(), 0xffffffff) #define MICROPY_END_ATOMIC_SECTION(x) (void)x; mp_thread_unix_end_atomic_section() #endif diff --git a/ports/unix/mpnimbleport.c b/ports/unix/mpnimbleport.c new file mode 100644 index 0000000000..8961910098 --- /dev/null +++ b/ports/unix/mpnimbleport.c @@ -0,0 +1,84 @@ +/* + * 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. + */ + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE + +#include "nimble/nimble_npl.h" + +#include "extmod/nimble/modbluetooth_nimble.h" +#include "extmod/nimble/hal/hal_uart.h" + +#define DEBUG_printf(...) // printf(__VA_ARGS__) + +// Called by the UART polling thread in mpbthciport.c. +bool mp_bluetooth_hci_poll(void) { + // DEBUG_printf("mp_bluetooth_hci_poll (unix nimble) %d\n", mp_bluetooth_nimble_ble_state); + + if (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { + DEBUG_printf("mp_bluetooth_hci_poll (unix nimble) -- shutdown\n"); + return false; + } + + if (mp_bluetooth_nimble_ble_state >= MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC) { + + // 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(); + + // Run pending background operations and events, but only after HCI sync. + mp_bluetooth_nimble_os_callout_process(); + 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 +} + +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"); +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE diff --git a/ports/unix/mpnimbleport.h b/ports/unix/mpnimbleport.h new file mode 100644 index 0000000000..a2935e6fdf --- /dev/null +++ b/ports/unix/mpnimbleport.h @@ -0,0 +1,33 @@ +/* + * 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. + */ + +#ifndef MICROPY_INCLUDED_UNIX_NIMBLE_PORT_H +#define MICROPY_INCLUDED_UNIX_NIMBLE_PORT_H + +#define MICROPY_HW_BLE_UART_ID (0) +#define MICROPY_HW_BLE_UART_BAUDRATE (1000000) + +#endif // MICROPY_INCLUDED_UNIX_NIMBLE_PORT_H diff --git a/ports/unix/variants/coverage/mpconfigvariant.mk b/ports/unix/variants/coverage/mpconfigvariant.mk index 66e694e0a9..f11d0b0d28 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.mk +++ b/ports/unix/variants/coverage/mpconfigvariant.mk @@ -17,4 +17,3 @@ MICROPY_ROM_TEXT_COMPRESSION = 1 MICROPY_VFS_FAT = 1 MICROPY_VFS_LFS1 = 1 MICROPY_VFS_LFS2 = 1 -MICROPY_PY_BLUETOOTH = 1 diff --git a/ports/unix/variants/dev/mpconfigvariant.mk b/ports/unix/variants/dev/mpconfigvariant.mk index 1f8611b6fc..91bd28da9b 100644 --- a/ports/unix/variants/dev/mpconfigvariant.mk +++ b/ports/unix/variants/dev/mpconfigvariant.mk @@ -6,4 +6,5 @@ MICROPY_ROM_TEXT_COMPRESSION = 1 MICROPY_VFS_FAT = 1 MICROPY_VFS_LFS1 = 1 MICROPY_VFS_LFS2 = 1 -MICROPY_PY_BLUETOOTH = 1 + +MICROPY_PY_BLUETOOTH ?= 1 From c4af714d58213bb23b81f3736f785d897e60ac77 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 14 Aug 2020 15:47:21 +1000 Subject: [PATCH 014/337] extmod/modbluetooth: Implement configuration of address modes. Changes `BLE.config('mac')` to return a tuple (addr_mode, addr). Adds `BLE.config(addr_mode=...)` to set the addressing mode. --- extmod/btstack/modbluetooth_btstack.c | 122 ++++++++++++++++++++++++- extmod/modbluetooth.c | 11 ++- extmod/modbluetooth.h | 21 ++--- extmod/nimble/modbluetooth_nimble.c | 127 +++++++++++++++++--------- ports/unix/mpbtstackport_common.c | 3 - 5 files changed, 222 insertions(+), 62 deletions(-) diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index 03ec12b734..b028a5d1ac 100644 --- a/extmod/btstack/modbluetooth_btstack.c +++ b/extmod/btstack/modbluetooth_btstack.c @@ -271,6 +271,14 @@ STATIC void btstack_packet_handler_att_server(uint8_t packet_type, uint16_t chan } } +#if MICROPY_BLUETOOTH_BTSTACK_ZEPHYR_STATIC_ADDRESS +// During startup, the controller (e.g. Zephyr) might give us a static address that we can use. +STATIC uint8_t controller_static_addr[6] = {0}; +STATIC bool controller_static_addr_available = false; + +STATIC const uint8_t read_static_address_command_complete_prefix[] = { 0x0e, 0x1b, 0x01, 0x09, 0xfc }; +#endif + STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t irq) { DEBUG_printf("btstack_packet_handler(packet_type=%u, packet=%p)\n", packet_type, packet); if (packet_type != HCI_EVENT_PACKET) { @@ -313,6 +321,13 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t DEBUG_printf(" --> hci transport packet sent\n"); } else if (event_type == HCI_EVENT_COMMAND_COMPLETE) { DEBUG_printf(" --> hci command complete\n"); + #if MICROPY_BLUETOOTH_BTSTACK_ZEPHYR_STATIC_ADDRESS + if (memcmp(packet, read_static_address_command_complete_prefix, sizeof(read_static_address_command_complete_prefix)) == 0) { + DEBUG_printf(" --> static address available\n"); + reverse_48(&packet[7], controller_static_addr); + controller_static_addr_available = true; + } + #endif // MICROPY_BLUETOOTH_BTSTACK_ZEPHYR_STATIC_ADDRESS } else if (event_type == HCI_EVENT_COMMAND_STATUS) { DEBUG_printf(" --> hci command status\n"); } else if (event_type == HCI_EVENT_NUMBER_OF_COMPLETED_PACKETS) { @@ -493,6 +508,60 @@ STATIC void btstack_init_deinit_timeout_handler(btstack_timer_source_t *ds) { mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_TIMEOUT; } +STATIC void btstack_static_address_ready(void *arg) { + DEBUG_printf("btstack_static_address_ready.\n"); + *(volatile bool *)arg = true; +} + +STATIC bool set_public_address(void) { + bd_addr_t local_addr; + gap_local_bd_addr(local_addr); + bd_addr_t null_addr = {0}; + if (memcmp(local_addr, null_addr, 6) == 0) { + DEBUG_printf("set_public_address: No public address available.\n"); + return false; + } + DEBUG_printf("set_public_address: Using controller's public address.\n"); + gap_random_address_set_mode(GAP_RANDOM_ADDRESS_TYPE_OFF); + return true; +} + +STATIC void set_random_address(void) { + #if MICROPY_BLUETOOTH_BTSTACK_ZEPHYR_STATIC_ADDRESS + if (controller_static_addr_available) { + DEBUG_printf("set_random_address: Using static address supplied by controller.\n"); + gap_random_address_set(controller_static_addr); + } else + #endif // MICROPY_BLUETOOTH_BTSTACK_ZEPHYR_STATIC_ADDRESS + { + DEBUG_printf("set_random_address: Generating random static address.\n"); + btstack_crypto_random_t sm_crypto_random_request; + bd_addr_t static_addr; + volatile bool ready = false; + btstack_crypto_random_generate(&sm_crypto_random_request, static_addr, 6, &btstack_static_address_ready, (void *)&ready); + while (!ready) { + MICROPY_EVENT_POLL_HOOK + } + DEBUG_printf("set_random_address: Address generated.\n"); + gap_random_address_set(static_addr); + } + + // Wait for the controller to accept this address. + while (true) { + uint8_t addr_type; + bd_addr_t addr; + gap_le_get_own_address(&addr_type, addr); + + bd_addr_t null_addr = {0}; + if (memcmp(addr, null_addr, 6) != 0) { + break; + } + + MICROPY_EVENT_POLL_HOOK + } + DEBUG_printf("set_random_address: Address loaded by controller\n"); +} + int mp_bluetooth_init(void) { DEBUG_printf("mp_bluetooth_init\n"); @@ -505,6 +574,10 @@ int mp_bluetooth_init(void) { btstack_memory_init(); + #if MICROPY_BLUETOOTH_BTSTACK_ZEPHYR_STATIC_ADDRESS + controller_static_addr_available = false; + #endif + MP_STATE_PORT(bluetooth_btstack_root_pointers) = m_new0(mp_bluetooth_btstack_root_pointers_t, 1); mp_bluetooth_gatts_db_create(&MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db); @@ -563,9 +636,23 @@ int mp_bluetooth_init(void) { // Clean up. MP_STATE_PORT(bluetooth_btstack_root_pointers) = NULL; + return MP_ETIMEDOUT; } + DEBUG_printf("mp_bluetooth_init: stack startup complete\n"); + + // At this point if the controller has its own public address, btstack will know this. + // However, if this is not available, then attempt to get a static address: + // - For a Zephyr controller on nRF, a static address will be available during startup. + // - Otherwise we ask the controller to generate a static address for us. + // In either case, calling gap_random_address_set will set the mode to STATIC, and then + // immediately set the address on the controller. We then wait until this address becomes available. + + if (!set_public_address()) { + set_random_address(); + } + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE // Enable GATT_EVENT_NOTIFICATION/GATT_EVENT_INDICATION for all connections and handles. gatt_client_listen_for_characteristic_value_updates(&MP_STATE_PORT(bluetooth_btstack_root_pointers)->notification, &btstack_packet_handler_generic, GATT_CLIENT_ANY_CONNECTION, NULL); @@ -612,8 +699,39 @@ bool mp_bluetooth_is_active(void) { return mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_ACTIVE; } -void mp_bluetooth_get_device_addr(uint8_t *addr) { - mp_hal_get_mac(MP_HAL_MAC_BDADDR, addr); +void mp_bluetooth_get_current_address(uint8_t *addr_type, uint8_t *addr) { + if (!mp_bluetooth_is_active()) { + mp_raise_OSError(ERRNO_BLUETOOTH_NOT_ACTIVE); + } + + DEBUG_printf("mp_bluetooth_get_current_address\n"); + gap_le_get_own_address(addr_type, addr); +} + +void mp_bluetooth_set_address_mode(uint8_t addr_mode) { + if (!mp_bluetooth_is_active()) { + mp_raise_OSError(ERRNO_BLUETOOTH_NOT_ACTIVE); + } + + switch (addr_mode) { + case MP_BLUETOOTH_ADDRESS_MODE_PUBLIC: { + DEBUG_printf("mp_bluetooth_set_address_mode: public\n"); + if (!set_public_address()) { + // No public address available. + mp_raise_OSError(MP_EINVAL); + } + break; + } + case MP_BLUETOOTH_ADDRESS_MODE_RANDOM: { + DEBUG_printf("mp_bluetooth_set_address_mode: random\n"); + set_random_address(); + break; + } + case MP_BLUETOOTH_ADDRESS_MODE_RPA: + case MP_BLUETOOTH_ADDRESS_MODE_NRPA: + // Not yet supported. + mp_raise_OSError(MP_EINVAL); + } } size_t mp_bluetooth_gap_get_device_name(const uint8_t **buf) { diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 82efe49385..a8a762ce32 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -308,9 +308,11 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map return mp_obj_new_bytes(buf, len); } case MP_QSTR_mac: { + uint8_t addr_type; uint8_t addr[6]; - mp_bluetooth_get_device_addr(addr); - return mp_obj_new_bytes(addr, MP_ARRAY_SIZE(addr)); + mp_bluetooth_get_current_address(&addr_type, addr); + 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: return mp_obj_new_int(self->ringbuf.size); @@ -366,6 +368,11 @@ 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; } + case MP_QSTR_addr_mode: { + mp_int_t addr_mode = mp_obj_get_int(e->value); + mp_bluetooth_set_address_mode(addr_mode); + break; + } default: mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); } diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 2c07683124..23ff97bc61 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -79,15 +79,6 @@ #define MP_BLUETOOTH_UUID_TYPE_32 (4) #define MP_BLUETOOTH_UUID_TYPE_128 (16) -// Address types (for the addr_type params). -// Ports will need to map these to their own values. -#define MP_BLUETOOTH_ADDR_PUBLIC (0x00) // Public (identity) address. (Same as NimBLE and NRF SD) -#define MP_BLUETOOTH_ADDR_RANDOM_STATIC (0x01) // Random static (identity) address. (Same as NimBLE and NRF SD) -#define MP_BLUETOOTH_ADDR_PUBLIC_ID (0x02) // (Same as NimBLE) -#define MP_BLUETOOTH_ADDR_RANDOM_ID (0x03) // (Same as NimBLE) -#define MP_BLUETOOTH_ADDR_RANDOM_PRIVATE_RESOLVABLE (0x12) // Random private resolvable address. (NRF SD 0x02) -#define MP_BLUETOOTH_ADDR_RANDOM_PRIVATE_NON_RESOLVABLE (0x13) // Random private non-resolvable address. (NRF SD 0x03) - // Event codes for the IRQ handler. #define MP_BLUETOOTH_IRQ_CENTRAL_CONNECT (1) #define MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT (2) @@ -110,6 +101,11 @@ #define MP_BLUETOOTH_IRQ_GATTC_INDICATE (19) #define MP_BLUETOOTH_IRQ_GATTS_INDICATE_DONE (20) +#define MP_BLUETOOTH_ADDRESS_MODE_PUBLIC (0) +#define MP_BLUETOOTH_ADDRESS_MODE_RANDOM (1) +#define MP_BLUETOOTH_ADDRESS_MODE_RPA (2) +#define MP_BLUETOOTH_ADDRESS_MODE_NRPA (3) + /* These aren't included in the module for space reasons, but can be used in your Python code if necessary. @@ -173,8 +169,11 @@ void mp_bluetooth_deinit(void); // Returns true when the Bluetooth stack is active. bool mp_bluetooth_is_active(void); -// Gets the MAC addr of this device in big-endian format. -void mp_bluetooth_get_device_addr(uint8_t *addr); +// Gets the current address of this device in big-endian format. +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); // 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); diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 61ba3a3aba..1c782943ab 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -50,6 +50,8 @@ #define ERRNO_BLUETOOTH_NOT_ACTIVE MP_ENODEV +STATIC uint8_t nimble_address_mode = BLE_OWN_ADDR_RANDOM; + // Any BLE_HS_xxx code not in this table will default to MP_EIO. STATIC int8_t ble_hs_err_to_errno_table[] = { [BLE_HS_EAGAIN] = MP_EAGAIN, @@ -148,9 +150,26 @@ STATIC void reset_cb(int reason) { (void)reason; } +STATIC bool has_public_address(void) { + return ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, NULL, NULL) == 0; +} + +STATIC void set_random_address(bool nrpa) { + int rc; + (void)rc; + DEBUG_printf("sync_cb: Generating random static address\n"); + ble_addr_t addr; + rc = ble_hs_id_gen_rnd(nrpa ? 1 : 0, &addr); + assert(rc == 0); + rc = ble_hs_id_set_rnd(addr.val); + assert(rc == 0); + rc = ble_hs_util_ensure_addr(1); + assert(rc == 0); +} + STATIC void sync_cb(void) { int rc; - ble_addr_t addr; + (void)rc; DEBUG_printf("sync_cb: state=%d\n", mp_bluetooth_nimble_ble_state); @@ -158,25 +177,11 @@ STATIC void sync_cb(void) { return; } - rc = ble_hs_util_ensure_addr(0); // prefer public address - if (rc != 0) { - // https://mynewt.apache.org/latest/tutorials/ble/eddystone.html#configure-the-nimble-stack-with-an-address - #if MICROPY_PY_BLUETOOTH_RANDOM_ADDR - rc = ble_hs_id_gen_rnd(1, &addr); - assert(rc == 0); - rc = ble_hs_id_set_rnd(addr.val); - assert(rc == 0); - #else - uint8_t addr_be[6]; - mp_hal_get_mac(MP_HAL_MAC_BDADDR, addr_be); - reverse_addr_byte_order(addr.val, addr_be); - // ble_hs_id_set_pub(addr.val); - rc = ble_hs_id_set_rnd(addr.val); - assert(rc == 0); - #endif - - rc = ble_hs_util_ensure_addr(0); // prefer public address - assert(rc == 0); + if (has_public_address()) { + nimble_address_mode = BLE_OWN_ADDR_PUBLIC; + } else { + nimble_address_mode = BLE_OWN_ADDR_RANDOM; + set_random_address(false); } if (MP_BLUETOOTH_DEFAULT_ATTR_LEN > 20) { @@ -412,19 +417,65 @@ bool mp_bluetooth_is_active(void) { return mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE; } -void mp_bluetooth_get_device_addr(uint8_t *addr) { - #if MICROPY_PY_BLUETOOTH_RANDOM_ADDR +void mp_bluetooth_get_current_address(uint8_t *addr_type, uint8_t *addr) { + if (!mp_bluetooth_is_active()) { + mp_raise_OSError(ERRNO_BLUETOOTH_NOT_ACTIVE); + } + uint8_t addr_le[6]; - int rc = ble_hs_id_copy_addr(BLE_ADDR_RANDOM, addr_le, NULL); + + switch (nimble_address_mode) { + case BLE_OWN_ADDR_PUBLIC: + *addr_type = BLE_ADDR_PUBLIC; + break; + case BLE_OWN_ADDR_RANDOM: + *addr_type = BLE_ADDR_RANDOM; + break; + case BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT: + case BLE_OWN_ADDR_RPA_RANDOM_DEFAULT: + default: + // TODO: If RPA/NRPA in use, get the current value. + // Is this even possible in NimBLE? + mp_raise_OSError(MP_EINVAL); + } + + int rc = ble_hs_id_copy_addr(*addr_type, addr_le, NULL); if (rc != 0) { - // Even with MICROPY_PY_BLUETOOTH_RANDOM_ADDR enabled the public address may - // be used instead, in which case there is no random address. - ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, addr_le, NULL); + mp_raise_OSError(MP_EINVAL); } reverse_addr_byte_order(addr, addr_le); - #else - mp_hal_get_mac(MP_HAL_MAC_BDADDR, addr); - #endif +} + +void mp_bluetooth_set_address_mode(uint8_t addr_mode) { + switch (addr_mode) { + case MP_BLUETOOTH_ADDRESS_MODE_PUBLIC: + if (!has_public_address()) { + // No public address available. + mp_raise_OSError(MP_EINVAL); + } + nimble_address_mode = BLE_OWN_ADDR_PUBLIC; + break; + case MP_BLUETOOTH_ADDRESS_MODE_RANDOM: + // Generate an static random address. + set_random_address(false); + nimble_address_mode = BLE_OWN_ADDR_RANDOM; + break; + case MP_BLUETOOTH_ADDRESS_MODE_RPA: + if (has_public_address()) { + nimble_address_mode = BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT; + } else { + // Generate an static random address to use as the identity address. + set_random_address(false); + nimble_address_mode = BLE_OWN_ADDR_RPA_RANDOM_DEFAULT; + } + break; + case MP_BLUETOOTH_ADDRESS_MODE_NRPA: + // Generate an NRPA. + set_random_address(true); + // In NimBLE, NRPA is treated like a static random address that happens to be an NRPA. + nimble_address_mode = BLE_OWN_ADDR_RANDOM; + break; + } } size_t mp_bluetooth_gap_get_device_name(const uint8_t **buf) { @@ -473,19 +524,7 @@ int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, cons .channel_map = 7, // all 3 channels. }; - ret = ble_gap_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, BLE_HS_FOREVER, &adv_params, gap_event_cb, NULL); - if (ret == 0) { - return 0; - } - ret = ble_gap_adv_start(BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT, NULL, BLE_HS_FOREVER, &adv_params, gap_event_cb, NULL); - if (ret == 0) { - return 0; - } - ret = ble_gap_adv_start(BLE_OWN_ADDR_RPA_RANDOM_DEFAULT, NULL, BLE_HS_FOREVER, &adv_params, gap_event_cb, NULL); - if (ret == 0) { - return 0; - } - ret = ble_gap_adv_start(BLE_OWN_ADDR_RANDOM, NULL, BLE_HS_FOREVER, &adv_params, gap_event_cb, NULL); + ret = ble_gap_adv_start(nimble_address_mode, NULL, BLE_HS_FOREVER, &adv_params, gap_event_cb, NULL); if (ret == 0) { return 0; } @@ -756,7 +795,7 @@ int mp_bluetooth_gap_scan_start(int32_t duration_ms, int32_t interval_us, int32_ .passive = active_scan ? 0 : 1, .filter_duplicates = 0, }; - int err = ble_gap_disc(BLE_OWN_ADDR_PUBLIC, duration_ms, &discover_params, gap_scan_cb, NULL); + int err = ble_gap_disc(nimble_address_mode, duration_ms, &discover_params, gap_scan_cb, NULL); return ble_hs_err_to_errno(err); } @@ -847,7 +886,7 @@ int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, }; ble_addr_t addr_nimble = create_nimble_addr(addr_type, addr); - int err = ble_gap_connect(BLE_OWN_ADDR_PUBLIC, &addr_nimble, duration_ms, ¶ms, &peripheral_gap_event_cb, NULL); + int err = ble_gap_connect(nimble_address_mode, &addr_nimble, duration_ms, ¶ms, &peripheral_gap_event_cb, NULL); return ble_hs_err_to_errno(err); } diff --git a/ports/unix/mpbtstackport_common.c b/ports/unix/mpbtstackport_common.c index eb1b1c9c80..621e661f9e 100644 --- a/ports/unix/mpbtstackport_common.c +++ b/ports/unix/mpbtstackport_common.c @@ -93,7 +93,4 @@ void mp_bluetooth_btstack_port_init(void) { #endif } -void mp_hal_get_mac(int idx, uint8_t buf[6]) { -} - #endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK From 26b66804e96b78395cddc37e195fc36862837d97 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 14 Aug 2020 15:16:29 +1000 Subject: [PATCH 015/337] tests/multi_bluetooth: Update to new config('mac') behaviour. --- tests/multi_bluetooth/ble_characteristic.py | 2 +- tests/multi_bluetooth/ble_gap_advertise.py | 2 +- tests/multi_bluetooth/ble_gap_connect.py | 4 ++-- tests/multi_bluetooth/ble_gap_device_name.py | 2 +- tests/multi_bluetooth/ble_gatt_data_transfer.py | 2 +- tests/multi_bluetooth/ble_gattc_discover_services.py | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/multi_bluetooth/ble_characteristic.py b/tests/multi_bluetooth/ble_characteristic.py index 0d72c181fd..d918d9aefc 100644 --- a/tests/multi_bluetooth/ble_characteristic.py +++ b/tests/multi_bluetooth/ble_characteristic.py @@ -146,7 +146,7 @@ def instance1(): try: # Connect to peripheral and then disconnect. print("gap_connect") - ble.gap_connect(0, BDADDR) + ble.gap_connect(*BDADDR) if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): return conn_handle, _, _ = waiting_data diff --git a/tests/multi_bluetooth/ble_gap_advertise.py b/tests/multi_bluetooth/ble_gap_advertise.py index 71a5b58a56..bb6ff8e425 100644 --- a/tests/multi_bluetooth/ble_gap_advertise.py +++ b/tests/multi_bluetooth/ble_gap_advertise.py @@ -36,7 +36,7 @@ def instance1(): def irq(ev, data): nonlocal finished, adv_types, adv_data if ev == _IRQ_SCAN_RESULT: - if data[1] == BDADDR: + if data[0] == BDADDR[0] and data[1] == BDADDR[1]: adv_types.add(data[2]) if adv_data is None: adv_data = bytes(data[4]) diff --git a/tests/multi_bluetooth/ble_gap_connect.py b/tests/multi_bluetooth/ble_gap_connect.py index 8e3ed8b816..ba9c28230d 100644 --- a/tests/multi_bluetooth/ble_gap_connect.py +++ b/tests/multi_bluetooth/ble_gap_connect.py @@ -76,7 +76,7 @@ def instance1(): try: # Connect to peripheral and then disconnect. print("gap_connect") - ble.gap_connect(0, BDADDR) + ble.gap_connect(*BDADDR) if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): return print("gap_disconnect:", ble.gap_disconnect(waiting_data[0])) @@ -88,7 +88,7 @@ def instance1(): # Connect to peripheral and then let the peripheral disconnect us. print("gap_connect") - ble.gap_connect(0, BDADDR) + ble.gap_connect(*BDADDR) if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): return wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS) diff --git a/tests/multi_bluetooth/ble_gap_device_name.py b/tests/multi_bluetooth/ble_gap_device_name.py index dafa367128..92ea94278a 100644 --- a/tests/multi_bluetooth/ble_gap_device_name.py +++ b/tests/multi_bluetooth/ble_gap_device_name.py @@ -97,7 +97,7 @@ def instance1(): # Connect to peripheral. print("gap_connect") - ble.gap_connect(0, BDADDR) + ble.gap_connect(*BDADDR) if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): return conn_handle, _, _ = waiting_data diff --git a/tests/multi_bluetooth/ble_gatt_data_transfer.py b/tests/multi_bluetooth/ble_gatt_data_transfer.py index dba0c333be..240f048607 100644 --- a/tests/multi_bluetooth/ble_gatt_data_transfer.py +++ b/tests/multi_bluetooth/ble_gatt_data_transfer.py @@ -137,7 +137,7 @@ def instance1(): try: # Connect to peripheral and then disconnect. print("gap_connect") - ble.gap_connect(0, BDADDR) + ble.gap_connect(*BDADDR) if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): return conn_handle, _, _ = waiting_data diff --git a/tests/multi_bluetooth/ble_gattc_discover_services.py b/tests/multi_bluetooth/ble_gattc_discover_services.py index e746b87458..57f95bf012 100644 --- a/tests/multi_bluetooth/ble_gattc_discover_services.py +++ b/tests/multi_bluetooth/ble_gattc_discover_services.py @@ -85,7 +85,7 @@ def instance1(): try: # Connect to peripheral and then disconnect. print("gap_connect") - ble.gap_connect(0, BDADDR) + ble.gap_connect(*BDADDR) if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): return conn_handle, _, _ = waiting_data From 67d8139e2b4cbaba5c722feef4e5478ae647344d Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 14 Aug 2020 15:17:14 +1000 Subject: [PATCH 016/337] docs/library/ubluetooth.rst: Document BLE address modes. --- docs/library/ubluetooth.rst | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/docs/library/ubluetooth.rst b/docs/library/ubluetooth.rst index 0cac16f5f4..f8dad7b8d1 100644 --- a/docs/library/ubluetooth.rst +++ b/docs/library/ubluetooth.rst @@ -45,12 +45,22 @@ Configuration Currently supported values are: - - ``'mac'``: Returns the device MAC address. If a device has a fixed address - (e.g. PYBD) then it will be returned. Otherwise (e.g. ESP32) a random - address will be generated when the BLE interface is made active. + - ``'mac'``: The current address in use, depending on the current address mode. + This returns a tuple of ``(addr_type, addr)``. - **Note:** on some ports, accessing this value requires that the interface is - active (so that the MAC address can be queried from the controller). + See :meth:`gatts_write ` for details about address type. + + This may only be queried while the interface is currently active. + + - ``'addr_mode'``: Sets the address mode. Values can be: + + * 0x00 - PUBLIC - Use the controller's public address. + * 0x01 - RANDOM - Use a generated static address. + * 0x02 - RPA - Use resolvable private addresses. + * 0x03 - NRPA - Use non-resolvable private addresses. + + By default the interface mode will use a PUBLIC address if available, otherwise + it will use a RANDOM address. - ``'gap_name'``: Get/set the GAP device name used by service 0x1800, characteristic 0x2a00. This can be set at any time and changed multiple @@ -219,8 +229,13 @@ Observer Role (Scanner) (background scanning). For each scan result the ``_IRQ_SCAN_RESULT`` event will be raised, with event - data ``(addr_type, addr, adv_type, rssi, adv_data)``. ``adv_type`` values correspond - to the Bluetooth Specification: + data ``(addr_type, addr, adv_type, rssi, adv_data)``. + + ``addr_type`` values indicate public or random addresses: + * 0x00 - PUBLIC + * 0x01 - RANDOM (either static, RPA, or NRPA, the type is encoded in the address itself) + + ``adv_type`` values correspond to the Bluetooth Specification: * 0x00 - ADV_IND - connectable and scannable undirected advertising * 0x01 - ADV_DIRECT_IND - connectable directed advertising @@ -342,6 +357,8 @@ Central Role (GATT Client) Connect to a peripheral. + See :meth:`gatts_write ` for details about address types. + On success, the ``_IRQ_PERIPHERAL_CONNECT`` event will be raised. .. method:: BLE.gap_disconnect(conn_handle) From 52a2ce45de353267776aaa25856c9d144653f142 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Mon, 17 Aug 2020 10:53:00 +1000 Subject: [PATCH 017/337] extmod/modbluetooth: Allow using mp_hal_get_mac as a static address. Generally a controller should either have its own public address hardcoded, or loaded by the driver (e.g. cywbt43). However, for a controller that has no public address where you still want a long-term stable address, this allows you to use a static address generated by the port. Typically on STM32 this will be an LAA, but a board might override this. --- extmod/btstack/modbluetooth_btstack.c | 17 ++++++++++++++++- extmod/nimble/modbluetooth_nimble.c | 18 +++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index b028a5d1ac..773234b9b2 100644 --- a/extmod/btstack/modbluetooth_btstack.c +++ b/extmod/btstack/modbluetooth_btstack.c @@ -508,10 +508,12 @@ STATIC void btstack_init_deinit_timeout_handler(btstack_timer_source_t *ds) { mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_TIMEOUT; } +#if !MICROPY_BLUETOOTH_USE_MP_HAL_GET_MAC_STATIC_ADDRESS STATIC void btstack_static_address_ready(void *arg) { DEBUG_printf("btstack_static_address_ready.\n"); *(volatile bool *)arg = true; } +#endif STATIC bool set_public_address(void) { bd_addr_t local_addr; @@ -534,14 +536,27 @@ STATIC void set_random_address(void) { } else #endif // MICROPY_BLUETOOTH_BTSTACK_ZEPHYR_STATIC_ADDRESS { + bd_addr_t static_addr; + + #if MICROPY_BLUETOOTH_USE_MP_HAL_GET_MAC_STATIC_ADDRESS + + DEBUG_printf("set_random_address: Generating static address using mp_hal_get_mac\n"); + mp_hal_get_mac(MP_HAL_MAC_BDADDR, static_addr); + // Mark it as STATIC (not RPA or NRPA). + static_addr[0] |= 0xc0; + + #else + DEBUG_printf("set_random_address: Generating random static address.\n"); btstack_crypto_random_t sm_crypto_random_request; - bd_addr_t static_addr; volatile bool ready = false; btstack_crypto_random_generate(&sm_crypto_random_request, static_addr, 6, &btstack_static_address_ready, (void *)&ready); while (!ready) { MICROPY_EVENT_POLL_HOOK } + + #endif // MICROPY_BLUETOOTH_USE_MP_HAL_GET_MAC_STATIC_ADDRESS + DEBUG_printf("set_random_address: Address generated.\n"); gap_random_address_set(static_addr); } diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 1c782943ab..4222f58fa8 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -157,10 +157,22 @@ STATIC bool has_public_address(void) { STATIC void set_random_address(bool nrpa) { int rc; (void)rc; - DEBUG_printf("sync_cb: Generating random static address\n"); ble_addr_t addr; - rc = ble_hs_id_gen_rnd(nrpa ? 1 : 0, &addr); - assert(rc == 0); + #if MICROPY_BLUETOOTH_USE_MP_HAL_GET_MAC_STATIC_ADDRESS + if (!nrpa) { + DEBUG_printf("set_random_address: Generating static address using mp_hal_get_mac\n"); + uint8_t hal_mac_addr[6]; + mp_hal_get_mac(MP_HAL_MAC_BDADDR, hal_mac_addr); + addr = create_nimble_addr(BLE_ADDR_RANDOM, hal_mac_addr); + // Mark it as STATIC (not RPA or NRPA). + addr.val[5] |= 0xc0; + } else + #endif + { + DEBUG_printf("set_random_address: Generating random static address\n"); + rc = ble_hs_id_gen_rnd(nrpa ? 1 : 0, &addr); + assert(rc == 0); + } rc = ble_hs_id_set_rnd(addr.val); assert(rc == 0); rc = ble_hs_util_ensure_addr(1); From 8b00aeab8fd20f8b8920cf322b5cfb7021ef1504 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sat, 15 Aug 2020 14:06:19 +1000 Subject: [PATCH 018/337] extmod/btstack: Add btstack support for _IRQ_GATTS_READ_REQUEST. --- extmod/btstack/modbluetooth_btstack.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index 773234b9b2..2e1bd47cfa 100644 --- a/extmod/btstack/modbluetooth_btstack.c +++ b/extmod/btstack/modbluetooth_btstack.c @@ -825,24 +825,37 @@ int mp_bluetooth_gatts_register_service_begin(bool append) { } STATIC uint16_t att_read_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) { + // Should return data length, 0 for error, or -1 for delayed response. + // For more details search "*att_read_callback*" in micropython/lib/btstack/doc/manual/docs/profiles.md (void)connection_handle; - DEBUG_printf("btstack: att_read_callback (handle: %u, offset: %u, buffer: %p, size: %u)\n", att_handle, offset, buffer, buffer_size); + DEBUG_printf("att_read_callback (handle: %u, offset: %u, buffer: %p, size: %u)\n", att_handle, offset, buffer, buffer_size); mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, att_handle); if (!entry) { - DEBUG_printf("btstack: att_read_callback handle not found\n"); - return 0; // TODO: Find status code for not-found. + DEBUG_printf("att_read_callback handle not found\n"); + return 0; } - return att_read_callback_handle_blob(entry->data, entry->data_len, offset, buffer, buffer_size); + #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. + 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; } STATIC int att_write_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) { (void)offset; (void)transaction_mode; - DEBUG_printf("btstack: att_write_callback (handle: %u, mode: %u, offset: %u, buffer: %p, size: %u)\n", att_handle, transaction_mode, offset, buffer, buffer_size); + DEBUG_printf("att_write_callback (handle: %u, mode: %u, offset: %u, buffer: %p, size: %u)\n", att_handle, transaction_mode, offset, buffer, buffer_size); mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, att_handle); if (!entry) { - DEBUG_printf("btstack: att_write_callback handle not found\n"); + DEBUG_printf("att_write_callback handle not found\n"); return 0; // TODO: Find status code for not-found. } From 6077c63a450d7c2ab5e31e5845ecb5f1a4634b26 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Sun, 16 Aug 2020 08:30:19 +1000 Subject: [PATCH 019/337] stm32/mpbthciport: Increase char timeout of BT HCI UART. The 2ms used previously was not long enough and it could lose HCI sync. Also print error on tx failure to make this more obvious in the future. --- ports/stm32/mpbthciport.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ports/stm32/mpbthciport.c b/ports/stm32/mpbthciport.c index 71a9e4fc7f..a5977ff12c 100644 --- a/ports/stm32/mpbthciport.c +++ b/ports/stm32/mpbthciport.c @@ -142,8 +142,9 @@ int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { mp_bluetooth_hci_uart_obj.base.type = &pyb_uart_type; mp_bluetooth_hci_uart_obj.uart_id = port; mp_bluetooth_hci_uart_obj.is_static = true; - mp_bluetooth_hci_uart_obj.timeout = 2; - mp_bluetooth_hci_uart_obj.timeout_char = 2; + // We don't want to block indefinitely, but expect flow control is doing its job. + mp_bluetooth_hci_uart_obj.timeout = 200; + 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. @@ -184,7 +185,11 @@ int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len) { // DEBUG_printf("mp_bluetooth_hci_uart_write (stm32)\n"); mp_bluetooth_hci_controller_wakeup(); - uart_tx_strn(&mp_bluetooth_hci_uart_obj, (void *)buf, len); + int errcode; + uart_tx_data(&mp_bluetooth_hci_uart_obj, (void *)buf, len, &errcode); + if (errcode != 0) { + printf("\nmp_bluetooth_hci_uart_write: failed to write to UART %d\n", errcode); + } return 0; } From 99a29ec705b463290d5a2ac1eabc46fb7f2a83b0 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 18 Aug 2020 11:05:34 +1000 Subject: [PATCH 020/337] extmod/btstack: Detect HCI UART init failure. --- extmod/btstack/btstack_hci_uart.c | 14 +++++++++++--- extmod/btstack/modbluetooth_btstack.c | 7 ++++++- ports/unix/mpbthciport.c | 4 ++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/extmod/btstack/btstack_hci_uart.c b/extmod/btstack/btstack_hci_uart.c index 5c96e02dc0..ae18628a9c 100644 --- a/extmod/btstack/btstack_hci_uart.c +++ b/extmod/btstack/btstack_hci_uart.c @@ -50,6 +50,7 @@ STATIC uint8_t *recv_buf; STATIC size_t recv_len; STATIC size_t recv_idx; STATIC void (*recv_handler)(void); +STATIC bool init_success = false; STATIC int btstack_uart_init(const btstack_uart_config_t *uart_config) { (void)uart_config; @@ -62,14 +63,21 @@ STATIC int btstack_uart_init(const btstack_uart_config_t *uart_config) { // Set up the UART peripheral, attach IRQ and power up the HCI controller. // We haven't been told the baud rate yet, so defer that until btstack_uart_set_baudrate. - mp_bluetooth_hci_uart_init(MICROPY_HW_BLE_UART_ID, 0); - mp_bluetooth_hci_controller_init(); + if (mp_bluetooth_hci_uart_init(MICROPY_HW_BLE_UART_ID, 0)) { + init_success = false; + return -1; + } + if (mp_bluetooth_hci_controller_init()) { + init_success = false; + return -1; + } + init_success = true; return 0; } STATIC int btstack_uart_open(void) { - return 0; + return init_success ? 0 : 1; } STATIC int btstack_uart_close(void) { diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index 2e1bd47cfa..cde802a61f 100644 --- a/extmod/btstack/modbluetooth_btstack.c +++ b/extmod/btstack/modbluetooth_btstack.c @@ -317,6 +317,9 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t // Signal that de-initialisation has completed. mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; } + } else if (event_type == BTSTACK_EVENT_POWERON_FAILED) { + // Signal that initialisation has failed. + mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; } else if (event_type == HCI_EVENT_TRANSPORT_PACKET_SENT) { DEBUG_printf(" --> hci transport packet sent\n"); } else if (event_type == HCI_EVENT_COMMAND_COMPLETE) { @@ -644,6 +647,8 @@ int mp_bluetooth_init(void) { if (mp_bluetooth_btstack_state != MP_BLUETOOTH_BTSTACK_STATE_ACTIVE) { DEBUG_printf("mp_bluetooth_init: stack startup timed out\n"); + bool timeout = mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_TIMEOUT; + // Required to stop the polling loop. mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; // Attempt a shutdown (may not do anything). @@ -652,7 +657,7 @@ int mp_bluetooth_init(void) { // Clean up. MP_STATE_PORT(bluetooth_btstack_root_pointers) = NULL; - return MP_ETIMEDOUT; + return timeout ? MP_ETIMEDOUT : MP_EINVAL; } DEBUG_printf("mp_bluetooth_init: stack startup complete\n"); diff --git a/ports/unix/mpbthciport.c b/ports/unix/mpbthciport.c index 5a5c1d4a4e..dbfb5e0d0e 100644 --- a/ports/unix/mpbthciport.c +++ b/ports/unix/mpbthciport.c @@ -124,12 +124,12 @@ int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { if (path != NULL) { strcpy(uart_device_name, path); } - DEBUG_printf("Using HCI UART: %s\n", uart_device_name); + DEBUG_printf("mp_bluetooth_hci_uart_init: Using HCI UART: %s\n", uart_device_name); int flags = O_RDWR | O_NOCTTY | O_NONBLOCK; uart_fd = open(uart_device_name, flags); if (uart_fd == -1) { - DEBUG_printf("Unable to open port %s", uart_device_name); + printf("mp_bluetooth_hci_uart_init: Unable to open port %s\n", uart_device_name); return -1; } From 311b8519af75be9fe4215b5ee8f77e91d089d5df Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Wed, 19 Aug 2020 10:46:09 +1000 Subject: [PATCH 021/337] esp32: Pin MicroPython and NimBLE tasks to core 0. MicroPython and NimBLE must be on the same core, for synchronisation of the BLE ringbuf and the MicroPython scheduler. However, in the current IDF versions (3.3 and 4.0) there are issues (see e.g. #5489) with running NimBLE on core 1. This change - pinning both tasks to core 0 - makes it possible to reliably run the BLE multitests on esp32 boards. --- ports/esp32/boards/sdkconfig.ble | 9 ++++++--- ports/esp32/mphalport.h | 7 +++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/ports/esp32/boards/sdkconfig.ble b/ports/esp32/boards/sdkconfig.ble index cdbb621a63..f714ce4629 100644 --- a/ports/esp32/boards/sdkconfig.ble +++ b/ports/esp32/boards/sdkconfig.ble @@ -9,9 +9,12 @@ CONFIG_BT_NIMBLE_ENABLED=y CONFIG_BT_NIMBLE_MAX_CONNECTIONS=4 # Pin to the same core as MP. -CONFIG_BT_NIMBLE_PINNED_TO_CORE_0=n -CONFIG_BT_NIMBLE_PINNED_TO_CORE_1=y -CONFIG_BT_NIMBLE_PINNED_TO_CORE=1 +# Until we move to IDF 4.2+, we need NimBLE on core 0, and for synchronisation +# with the ringbuffer and scheduler MP needs to be on the same core. +# See https://github.com/micropython/micropython/issues/5489 +CONFIG_BT_NIMBLE_PINNED_TO_CORE_0=y +CONFIG_BT_NIMBLE_PINNED_TO_CORE_1=n +CONFIG_BT_NIMBLE_PINNED_TO_CORE=0 # v3.3-only (renamed in 4.0) CONFIG_BTDM_CONTROLLER_MODE_BLE_ONLY=y diff --git a/ports/esp32/mphalport.h b/ports/esp32/mphalport.h index 1f78d820a3..60cc308d68 100644 --- a/ports/esp32/mphalport.h +++ b/ports/esp32/mphalport.h @@ -35,8 +35,11 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" -// The core that the MicroPython task(s) are pinned to -#define MP_TASK_COREID (1) +// The core that the MicroPython task(s) are pinned to. +// Until we move to IDF 4.2+, we need NimBLE on core 0, and for synchronisation +// with the ringbuffer and scheduler MP needs to be on the same core. +// See https://github.com/micropython/micropython/issues/5489 +#define MP_TASK_COREID (0) extern TaskHandle_t mp_main_task_handle; From 126f972c34f25bffc07d0d1cb3e536921fec0979 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Wed, 19 Aug 2020 12:55:38 +1000 Subject: [PATCH 022/337] extmod/nimble: Add timeout for HCI sync on startup. This allows `ble.active(1)` to fail correctly if the HCI controller is unavailable. It also avoids an infine loop in the NimBLE event handler where NimBLE doesn't correctly detect that the HCI controller is unavailable and keeps trying to reset. Furthermore, it fixes an issue where GATT service registrations were left allocated, which led to a bad realloc if the stack was activated multiple times. --- extmod/nimble/modbluetooth_nimble.c | 28 +++++++++++++++++++++------- extmod/nimble/nimble/nimble_npl_os.c | 15 ++++++++++++--- ports/unix/mpbthciport.c | 16 ++++++++++++++++ 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 4222f58fa8..9ca90b6024 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -52,6 +52,9 @@ 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[] = { [BLE_HS_EAGAIN] = MP_EAGAIN, @@ -206,6 +209,8 @@ 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) { @@ -355,6 +360,8 @@ 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); @@ -370,21 +377,28 @@ int mp_bluetooth_init(void) { // Initialise NimBLE memory and data structures. nimble_port_init(); - // By default, just register the default gap/gatt service. - ble_svc_gap_init(); - ble_svc_gatt_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; - // Wait for sync callback - while (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE) { - MICROPY_EVENT_POLL_HOOK + ble_npl_sem_pend(&startup_sem, NIMBLE_STARTUP_TIMEOUT); + + if (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE) { + mp_bluetooth_deinit(); + return MP_ETIMEDOUT; } + // By default, just register the default gap/gatt service. + ble_svc_gap_init(); + ble_svc_gatt_init(); + // The preceeding two calls allocate service definitions on the heap, + // then we must now call gatts_start to register those services + // and free the heap memory. + // Otherwise it will be realloc'ed on the next stack startup. + ble_gatts_start(); + DEBUG_printf("mp_bluetooth_init: ready\n"); return 0; diff --git a/extmod/nimble/nimble/nimble_npl_os.c b/extmod/nimble/nimble/nimble_npl_os.c index 46f7a0b291..2ec012940f 100644 --- a/extmod/nimble/nimble/nimble_npl_os.c +++ b/extmod/nimble/nimble/nimble_npl_os.c @@ -32,6 +32,7 @@ #include "extmod/nimble/hal/hal_uart.h" #include "extmod/modbluetooth.h" +#include "extmod/nimble/modbluetooth_nimble.h" #define DEBUG_OS_printf(...) // printf(__VA_ARGS__) #define DEBUG_MALLOC_printf(...) // printf(__VA_ARGS__) @@ -180,7 +181,8 @@ struct ble_npl_eventq *global_eventq = NULL; void mp_bluetooth_nimble_os_eventq_run_all(void) { for (struct ble_npl_eventq *evq = global_eventq; evq != NULL; evq = evq->nextq) { - while (evq->head != NULL) { + 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) { @@ -191,6 +193,13 @@ void mp_bluetooth_nimble_os_eventq_run_all(void) { 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). + break; + } } } } @@ -348,11 +357,11 @@ ble_npl_error_t ble_npl_sem_pend(struct ble_npl_sem *sem, ble_npl_time_t timeout } if (sem->count == 0) { - printf("NimBLE: HCI ACK timeout\n"); + DEBUG_SEM_printf("ble_npl_sem_pend: semaphore timeout\n"); return BLE_NPL_TIMEOUT; } - DEBUG_SEM_printf("got response in %u ms\n", (int)(mp_hal_ticks_ms() - t0)); + DEBUG_SEM_printf("ble_npl_sem_pend: acquired in %u ms\n", (int)(mp_hal_ticks_ms() - t0)); } sem->count -= 1; return BLE_NPL_OK; diff --git a/ports/unix/mpbthciport.c b/ports/unix/mpbthciport.c index dbfb5e0d0e..316a8831fe 100644 --- a/ports/unix/mpbthciport.c +++ b/ports/unix/mpbthciport.c @@ -105,6 +105,10 @@ STATIC int configure_uart(void) { // Apply immediately. if (tcsetattr(uart_fd, TCSANOW, &toptions) < 0) { DEBUG_printf("Couldn't set term attributes"); + + close(uart_fd); + uart_fd = -1; + return -1; } @@ -149,6 +153,10 @@ 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\n"); + if (uart_fd == -1) { + return 0; + } + // Wait for the poll loop to terminate when the state is set to OFF. pthread_join(hci_poll_thread_id, NULL); @@ -168,6 +176,10 @@ int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate) { int mp_bluetooth_hci_uart_readchar(void) { // DEBUG_printf("mp_bluetooth_hci_uart_readchar\n"); + if (uart_fd == -1) { + return -1; + } + uint8_t c; ssize_t bytes_read = read(uart_fd, &c, 1); @@ -184,6 +196,10 @@ int mp_bluetooth_hci_uart_readchar(void) { int mp_bluetooth_hci_uart_write(const uint8_t *buf, size_t len) { // DEBUG_printf("mp_bluetooth_hci_uart_write\n"); + if (uart_fd == -1) { + return 0; + } + #if DEBUG_HCI_DUMP printf("[% 8ld] TX: %02x", mp_hal_ticks_ms(), buf[0]); for (size_t i = 1; i < len; ++i) { From b27edb8073409a3caee921dd4e2c1b182bd4c0fb Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Thu, 20 Aug 2020 23:21:06 +1000 Subject: [PATCH 023/337] stm32/make-stmconst.py: Add support for WB55 header files. --- ports/stm32/make-stmconst.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/ports/stm32/make-stmconst.py b/ports/stm32/make-stmconst.py index 1b3cc08d95..ac5c56f5c7 100644 --- a/ports/stm32/make-stmconst.py +++ b/ports/stm32/make-stmconst.py @@ -46,7 +46,7 @@ class LexerError(Exception): class Lexer: re_io_reg = r"__IO uint(?P8|16|32)_t +(?P[A-Z0-9]+)" - re_comment = r"(?P[A-Za-z0-9 \-/_()&]+)" + re_comment = r"(?P[A-Za-z0-9 \-/_()&:]+)" re_addr_offset = r"Address offset: (?P0x[0-9A-Z]{2,3})" regexs = ( ( @@ -78,16 +78,16 @@ class Lexer: ( "IO reg", re.compile( - re_io_reg + r"; */\*!< *" + re_comment + r", +" + re_addr_offset + r" *\*/" + re_io_reg + r" *; */\*!< *" + re_comment + r",? +" + re_addr_offset + r" *\*/" ), ), ( "IO reg array", re.compile( re_io_reg - + r"\[(?P[2-8])\]; */\*!< *" + + r"\[(?P[2-8])\] *; */\*!< *" + re_comment - + r", +" + + r",? +" + re_addr_offset + r"-(0x[0-9A-Z]{2,3}) *\*/" ), @@ -160,7 +160,11 @@ def parse_file(filename): if m[0] == "}": pass elif m[0] == "} TypeDef": - reg_defs[m[1].groupdict()["id"]] = regs + d = m[1].groupdict() + n = d["id"] + g = d["global"] + if n not in reg_defs or not g: + reg_defs[n] = regs else: raise LexerError(lexer.line_number) @@ -298,6 +302,7 @@ def main(): "USART", "WWDG", "RNG", + "IPCC", ): if reg in reg_defs: print_regs(reg, reg_defs[reg], needed_qstrs, needed_mpzs) From 30e8162ac49d333b00bae4a7f719a1f24692b062 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Thu, 20 Aug 2020 18:12:44 +1000 Subject: [PATCH 024/337] stm32/rfcore: Update rfcore.c to match how ST examples work. - Split tables and buffers into SRAM2A/2B. - Use structs rather than word offsets to access tables. - Use FLASH_IPCCDBA register value rather than option bytes directly. --- ports/stm32/boards/stm32wb55xg.ld | 13 ++- ports/stm32/rfcore.c | 172 ++++++++++++++++++++++-------- 2 files changed, 138 insertions(+), 47 deletions(-) diff --git a/ports/stm32/boards/stm32wb55xg.ld b/ports/stm32/boards/stm32wb55xg.ld index dbeccc1895..c3dc5f5197 100644 --- a/ports/stm32/boards/stm32wb55xg.ld +++ b/ports/stm32/boards/stm32wb55xg.ld @@ -9,7 +9,8 @@ MEMORY FLASH_APP (rx) : ORIGIN = 0x08004000, LENGTH = 496K /* sectors 4-127 */ FLASH_FS (r) : ORIGIN = 0x08080000, LENGTH = 256K /* sectors 128-191 */ RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K /* SRAM1 */ - RAM2A (xrw) : ORIGIN = 0x20030020, LENGTH = 8K /* SRAM2A */ + RAM2A (xrw) : ORIGIN = 0x20030000, LENGTH = 10K /* SRAM2A */ + RAM2B (xrw) : ORIGIN = 0x20038000, LENGTH = 10K /* SRAM2B */ } /* produce a link error if there is not this amount of RAM for these sections */ @@ -36,10 +37,20 @@ _flash_fs_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); SECTIONS { + /* Put all IPCC tables into SRAM2A. */ .ram2a_bss : { . = ALIGN(4); + . = . + 64; /* Leave room for the mb_ref_table_t (assuming IPCCDBA==0). */ *rfcore.o(.bss.ipcc_mem_*) . = ALIGN(4); } >RAM2A + + /* Put all IPCC buffers into SRAM2B. */ + .ram2b_bss : + { + . = ALIGN(4); + *rfcore.o(.bss.ipcc_membuf_*) + . = ALIGN(4); + } >RAM2B } diff --git a/ports/stm32/rfcore.c b/ports/stm32/rfcore.c index 54b3393435..3565c03898 100644 --- a/ports/stm32/rfcore.c +++ b/ports/stm32/rfcore.c @@ -4,6 +4,7 @@ * The MIT License (MIT) * * Copyright (c) 2019 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 @@ -34,6 +35,10 @@ #if defined(STM32WB) +#include "stm32wbxx_ll_ipcc.h" + +#define DEBUG_printf(...) // printf("rfcore: " __VA_ARGS__) + // Define to 1 to print traces of HCI packets #define HCI_TRACE (0) @@ -61,23 +66,88 @@ typedef struct _parse_hci_info_t { bool was_hci_reset_evt; } parse_hci_info_t; -static volatile uint32_t ipcc_mem_dev_info_tab[8]; -static volatile uint32_t ipcc_mem_ble_tab[4]; -static volatile uint32_t ipcc_mem_sys_tab[2]; -static volatile uint32_t ipcc_mem_memmgr_tab[7]; +// Version +// [0:3] = Build - 0: Untracked - 15:Released - x: Tracked version +// [4:7] = branch - 0: Mass Market - x: ... +// [8:15] = Subversion +// [16:23] = Version minor +// [24:31] = Version major -static volatile uint32_t ipcc_mem_sys_cmd_buf[272 / 4]; -static volatile tl_list_node_t ipcc_mem_sys_queue; +// Memory Size +// [0:7] = Flash (Number of 4k sectors) +// [8:15] = Reserved (Shall be set to 0 - may be used as flash extension) +// [16:23] = SRAM2b (Number of 1k sectors) +// [24:31] = SRAM2a (Number of 1k sectors) -static volatile tl_list_node_t ipcc_mem_memmgr_free_buf_queue; -static volatile uint32_t ipcc_mem_memmgr_ble_spare_evt_buf[272 / 4]; -static volatile uint32_t ipcc_mem_memmgr_sys_spare_evt_buf[272 / 4]; -static volatile uint32_t ipcc_mem_memmgr_evt_pool[6 * 272 / 4]; +typedef struct __attribute__((packed)) _ipcc_device_info_table_t { + uint32_t safeboot_version; + uint32_t fus_version; + uint32_t fus_memorysize; + uint32_t fus_info; + uint32_t fw_version; + uint32_t fw_memorysize; + uint32_t fw_infostack; + uint32_t fw_reserved; +} ipcc_device_info_table_t; -static volatile uint32_t ipcc_mem_ble_cmd_buf[272 / 4]; -static volatile uint32_t ipcc_mem_ble_cs_buf[272 / 4]; -static volatile tl_list_node_t ipcc_mem_ble_evt_queue; -static volatile uint32_t ipcc_mem_ble_hci_acl_data_buf[272 / 4]; +typedef struct __attribute__((packed)) _ipcc_ble_table_t { + uint8_t *pcmd_buffer; + uint8_t *pcs_buffer; + tl_list_node_t *pevt_queue; + uint8_t *phci_acl_data_buffer; +} ipcc_ble_table_t; + +// msg +// [0:7] = cmd/evt +// [8:31] = Reserved +typedef struct __attribute__((packed)) _ipcc_sys_table_t { + uint8_t *pcmd_buffer; + tl_list_node_t *sys_queue; +} ipcc_sys_table_t; + +typedef struct __attribute__((packed)) _ipcc_mem_manager_table_t { + uint8_t *spare_ble_buffer; + uint8_t *spare_sys_buffer; + uint8_t *blepool; + uint32_t blepoolsize; + tl_list_node_t *pevt_free_buffer_queue; + uint8_t *traces_evt_pool; + uint32_t tracespoolsize; +} ipcc_mem_manager_table_t; + +typedef struct __attribute__((packed)) _ipcc_ref_table_t { + ipcc_device_info_table_t *p_device_info_table; + ipcc_ble_table_t *p_ble_table; + void *p_thread_table; + ipcc_sys_table_t *p_sys_table; + ipcc_mem_manager_table_t *p_mem_manager_table; + void *p_traces_table; + void *p_mac_802_15_4_table; + void *p_zigbee_table; + void *p_lld_tests_table; + void *p_lld_ble_table; +} ipcc_ref_table_t; + +// The stm32wb55xg.ld script puts .bss.ipcc_mem_* into SRAM2A and .bss_ipcc_membuf_* into SRAM2B. +// It also leaves 64 bytes at the start of SRAM2A for the ref table. + +STATIC ipcc_device_info_table_t ipcc_mem_dev_info_tab; // mem1 +STATIC ipcc_ble_table_t ipcc_mem_ble_tab; // mem1 +STATIC ipcc_sys_table_t ipcc_mem_sys_tab; // mem1 +STATIC ipcc_mem_manager_table_t ipcc_mem_memmgr_tab; // mem1 + +STATIC uint8_t ipcc_membuf_sys_cmd_buf[272]; // mem2 +STATIC tl_list_node_t ipcc_mem_sys_queue; // mem1 + +STATIC tl_list_node_t ipcc_mem_memmgr_free_buf_queue; // mem1 +STATIC uint8_t ipcc_membuf_memmgr_ble_spare_evt_buf[272]; // mem2 +STATIC uint8_t ipcc_membuf_memmgr_sys_spare_evt_buf[272]; // mem2 +STATIC uint8_t ipcc_membuf_memmgr_evt_pool[6 * 272]; // mem2 + +STATIC uint8_t ipcc_membuf_ble_cmd_buf[272]; // mem2 +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 /******************************************************************************/ // Transport layer linked list @@ -105,21 +175,21 @@ STATIC void tl_list_append(volatile tl_list_node_t *head, volatile tl_list_node_ /******************************************************************************/ // IPCC interface -STATIC uint32_t get_ipccdba(void) { - return *(uint32_t *)(OPTION_BYTE_BASE + 0x68) & 0x3fff; -} - -STATIC volatile void **get_buffer_table(void) { - return (volatile void **)(SRAM2A_BASE + get_ipccdba()); +STATIC volatile ipcc_ref_table_t *get_buffer_table(void) { + // The IPCCDBA option bytes must not be changed without + // making a corresponding change to the linker script. + return (volatile ipcc_ref_table_t *)(SRAM2A_BASE + LL_FLASH_GetIPCCBufferAddr() * 4); } void ipcc_init(uint32_t irq_pri) { + DEBUG_printf("ipcc_init\n"); + // Setup buffer table pointers - volatile void **tab = get_buffer_table(); - tab[0] = &ipcc_mem_dev_info_tab[0]; - tab[1] = &ipcc_mem_ble_tab[0]; - tab[3] = &ipcc_mem_sys_tab[0]; - tab[4] = &ipcc_mem_memmgr_tab[0]; + volatile ipcc_ref_table_t *tab = get_buffer_table(); + tab->p_device_info_table = &ipcc_mem_dev_info_tab; + tab->p_ble_table = &ipcc_mem_ble_tab; + tab->p_sys_table = &ipcc_mem_sys_tab; + tab->p_mem_manager_table = &ipcc_mem_memmgr_tab; // Start IPCC peripheral __HAL_RCC_IPCC_CLK_ENABLE(); @@ -130,32 +200,34 @@ void ipcc_init(uint32_t irq_pri) { NVIC_SetPriority(IPCC_C1_RX_IRQn, irq_pri); HAL_NVIC_EnableIRQ(IPCC_C1_RX_IRQn); - // Device info table will be populated by FUS/WS + // Device info table will be populated by FUS/WS on CPU2 boot. // Populate system table tl_list_init(&ipcc_mem_sys_queue); - ipcc_mem_sys_tab[0] = (uint32_t)&ipcc_mem_sys_cmd_buf[0]; - ipcc_mem_sys_tab[1] = (uint32_t)&ipcc_mem_sys_queue; + ipcc_mem_sys_tab.pcmd_buffer = ipcc_membuf_sys_cmd_buf; + ipcc_mem_sys_tab.sys_queue = &ipcc_mem_sys_queue; // Populate memory manager table tl_list_init(&ipcc_mem_memmgr_free_buf_queue); - ipcc_mem_memmgr_tab[0] = (uint32_t)&ipcc_mem_memmgr_ble_spare_evt_buf[0]; - ipcc_mem_memmgr_tab[1] = (uint32_t)&ipcc_mem_memmgr_sys_spare_evt_buf[0]; - ipcc_mem_memmgr_tab[2] = (uint32_t)&ipcc_mem_memmgr_evt_pool[0]; - ipcc_mem_memmgr_tab[3] = sizeof(ipcc_mem_memmgr_evt_pool); - ipcc_mem_memmgr_tab[4] = (uint32_t)&ipcc_mem_memmgr_free_buf_queue; - ipcc_mem_memmgr_tab[5] = 0; - ipcc_mem_memmgr_tab[6] = 0; + ipcc_mem_memmgr_tab.spare_ble_buffer = ipcc_membuf_memmgr_ble_spare_evt_buf; + ipcc_mem_memmgr_tab.spare_sys_buffer = ipcc_membuf_memmgr_sys_spare_evt_buf; + ipcc_mem_memmgr_tab.blepool = ipcc_membuf_memmgr_evt_pool; + ipcc_mem_memmgr_tab.blepoolsize = sizeof(ipcc_membuf_memmgr_evt_pool); + ipcc_mem_memmgr_tab.pevt_free_buffer_queue = &ipcc_mem_memmgr_free_buf_queue; + ipcc_mem_memmgr_tab.traces_evt_pool = NULL; + ipcc_mem_memmgr_tab.tracespoolsize = 0; // Populate BLE table tl_list_init(&ipcc_mem_ble_evt_queue); - ipcc_mem_ble_tab[0] = (uint32_t)&ipcc_mem_ble_cmd_buf[0]; - ipcc_mem_ble_tab[1] = (uint32_t)&ipcc_mem_ble_cs_buf[0]; - ipcc_mem_ble_tab[2] = (uint32_t)&ipcc_mem_ble_evt_queue; - ipcc_mem_ble_tab[3] = (uint32_t)&ipcc_mem_ble_hci_acl_data_buf[0]; + ipcc_mem_ble_tab.pcmd_buffer = ipcc_membuf_ble_cmd_buf; + ipcc_mem_ble_tab.pcs_buffer = ipcc_membuf_ble_cs_buf; + ipcc_mem_ble_tab.pevt_queue = &ipcc_mem_ble_evt_queue; + ipcc_mem_ble_tab.phci_acl_data_buffer = ipcc_membuf_ble_hci_acl_data_buf; } STATIC int ipcc_wait_ack(unsigned int ch, uint32_t timeout_ms) { + DEBUG_printf("ipcc_wait_ack\n"); + uint32_t t0 = mp_hal_ticks_ms(); while (IPCC->C1TOC2SR & ch) { if (mp_hal_ticks_ms() - t0 > timeout_ms) { @@ -168,6 +240,8 @@ STATIC int ipcc_wait_ack(unsigned int ch, uint32_t timeout_ms) { } STATIC int ipcc_wait_msg(unsigned int ch, uint32_t timeout_ms) { + DEBUG_printf("ipcc_wait_msg\n"); + uint32_t t0 = mp_hal_ticks_ms(); while (!(IPCC->C2TOC1SR & ch)) { if (mp_hal_ticks_ms() - t0 > timeout_ms) { @@ -254,8 +328,8 @@ STATIC void tl_check_msg(volatile tl_list_node_t *head, unsigned int ch, parse_h while (cur != head) { tl_parse_hci_msg((uint8_t *)cur->body, parse); volatile tl_list_node_t *next = tl_list_unlink(cur); - if ((void *)&ipcc_mem_memmgr_evt_pool[0] <= (void *)cur - && (void *)cur < (void *)&ipcc_mem_memmgr_evt_pool[MP_ARRAY_SIZE(ipcc_mem_memmgr_evt_pool)]) { + if ((void *)&ipcc_membuf_memmgr_evt_pool[0] <= (void *)cur + && (void *)cur < (void *)&ipcc_membuf_memmgr_evt_pool[MP_ARRAY_SIZE(ipcc_membuf_memmgr_evt_pool)]) { // Place memory back in free pool tl_list_append(&ipcc_mem_memmgr_free_buf_queue, cur); free = true; @@ -291,12 +365,12 @@ STATIC void tl_sys_wait_resp(const uint8_t *buf, unsigned int ch) { } STATIC void tl_sys_hci_cmd_resp(uint16_t opcode, size_t len, const uint8_t *buf) { - tl_hci_cmd((uint8_t *)&ipcc_mem_sys_cmd_buf, IPCC_CH_SYS, 0x10, opcode, len, buf); - tl_sys_wait_resp((uint8_t *)&ipcc_mem_sys_cmd_buf, IPCC_CH_SYS); + tl_hci_cmd((uint8_t *)&ipcc_membuf_sys_cmd_buf, IPCC_CH_SYS, 0x10, opcode, len, buf); + tl_sys_wait_resp((uint8_t *)&ipcc_membuf_sys_cmd_buf, IPCC_CH_SYS); } STATIC void tl_ble_hci_cmd_resp(uint16_t opcode, size_t len, const uint8_t *buf) { - tl_hci_cmd((uint8_t *)&ipcc_mem_ble_cmd_buf[0], IPCC_CH_BLE, 0x01, opcode, len, buf); + tl_hci_cmd((uint8_t *)&ipcc_membuf_ble_cmd_buf[0], IPCC_CH_BLE, 0x01, opcode, len, buf); ipcc_wait_msg(IPCC_CH_BLE, 250); tl_check_msg(&ipcc_mem_ble_evt_queue, IPCC_CH_BLE, NULL); } @@ -305,6 +379,8 @@ STATIC void tl_ble_hci_cmd_resp(uint16_t opcode, size_t len, const uint8_t *buf) // RF core interface void rfcore_init(void) { + DEBUG_printf("rfcore_init\n"); + // Ensure LSE is running rtc_init_finalise(); @@ -361,6 +437,8 @@ static const struct { }; void rfcore_ble_init(void) { + DEBUG_printf("rfcore_ble_init\n"); + // Clear any outstanding messages from ipcc_init tl_check_msg(&ipcc_mem_sys_queue, IPCC_CH_SYS, NULL); tl_check_msg(&ipcc_mem_ble_evt_queue, IPCC_CH_BLE, NULL); @@ -371,6 +449,8 @@ void rfcore_ble_init(void) { } void rfcore_ble_hci_cmd(size_t len, const uint8_t *src) { + DEBUG_printf("rfcore_ble_hci_cmd\n"); + #if HCI_TRACE printf("[% 8d] HCI_CMD(%02x", mp_hal_ticks_ms(), src[0]); for (int i = 1; i < len; ++i) { @@ -382,10 +462,10 @@ void rfcore_ble_hci_cmd(size_t len, const uint8_t *src) { tl_list_node_t *n; uint32_t ch; if (src[0] == 0x01) { - n = (tl_list_node_t *)&ipcc_mem_ble_cmd_buf[0]; + n = (tl_list_node_t *)&ipcc_membuf_ble_cmd_buf[0]; ch = IPCC_CH_BLE; } else if (src[0] == 0x02) { - n = (tl_list_node_t *)&ipcc_mem_ble_hci_acl_data_buf[0]; + n = (tl_list_node_t *)&ipcc_membuf_ble_hci_acl_data_buf[0]; ch = IPCC_CH_HCI_ACL; } else { printf("** UNEXPECTED HCI HDR: 0x%02x **\n", src[0]); From 9c9cc7a02f6f998d0141989040d68219d6ad0625 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 8 Sep 2020 17:00:41 +1000 Subject: [PATCH 025/337] stm32/boards/USBDONGLE_WB55: Add USE_MBOOT support. --- ports/stm32/boards/USBDONGLE_WB55/mpconfigboard.mk | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/USBDONGLE_WB55/mpconfigboard.mk b/ports/stm32/boards/USBDONGLE_WB55/mpconfigboard.mk index 416364df9e..dcec788ed4 100644 --- a/ports/stm32/boards/USBDONGLE_WB55/mpconfigboard.mk +++ b/ports/stm32/boards/USBDONGLE_WB55/mpconfigboard.mk @@ -1,9 +1,18 @@ MCU_SERIES = wb CMSIS_MCU = STM32WB55xx AF_FILE = boards/stm32wb55_af.csv -LD_FILES = boards/stm32wb55xg.ld boards/common_basic.ld STARTUP_FILE = lib/stm32lib/CMSIS/STM32WBxx/Source/Templates/gcc/startup_stm32wb55xx_cm4.o +ifeq ($(USE_MBOOT),1) +# When using Mboot all the text goes together after the bootloader +LD_FILES = boards/stm32wb55xg.ld boards/common_bl.ld +TEXT0_ADDR = 0x08004000 +else +# When not using Mboot the text goes at the start of flash +LD_FILES = boards/stm32wb55xg.ld boards/common_basic.ld +TEXT0_ADDR = 0x08000000 +endif + # MicroPython settings MICROPY_PY_BLUETOOTH = 1 MICROPY_BLUETOOTH_NIMBLE = 1 From 0f28020a68b190d973dc5ef2049c76eb28acddee Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 28 Aug 2020 14:59:24 +1000 Subject: [PATCH 026/337] stm32/powerctrlboot: Acquire HSEM5 on STM32WB during SystemClock_Config. This is required to allow using WS firmware newer than 1.1.1 concurrently with USB (e.g. USB VCP). It prevents CPU2 from modifying the CLK48 config on boot. Tested on WS=1.8 FUS=1.1. See AN5289 and https://github.com/micropython/micropython/issues/6316 --- ports/stm32/powerctrlboot.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ports/stm32/powerctrlboot.c b/ports/stm32/powerctrlboot.c index 206b19b750..880e43e04c 100644 --- a/ports/stm32/powerctrlboot.c +++ b/ports/stm32/powerctrlboot.c @@ -154,12 +154,23 @@ void SystemClock_Config(void) { #elif defined(STM32WB) +#include "stm32wbxx_ll_hsem.h" + +// This semaphore protected access to the CLK48 configuration. +// CPU1 should hold this semaphore while the USB peripheral is in use. +// See AN5289 and https://github.com/micropython/micropython/issues/6316. +#define CLK48_SEMID (5) + void SystemClock_Config(void) { // Enable the 32MHz external oscillator RCC->CR |= RCC_CR_HSEON; while (!(RCC->CR & RCC_CR_HSERDY)) { } + // Prevent CPU2 from disabling CLK48. + while (LL_HSEM_1StepLock(HSEM, CLK48_SEMID)) { + } + // Use HSE and the PLL to get a 64MHz SYSCLK #define PLLM (HSE_VALUE / 8000000) // VCO input is 8MHz #define PLLN (24) // 24*8MHz = 192MHz From 5eda362e0a7ed0eda6a4becdebf76a53cf69f944 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Mon, 31 Aug 2020 15:54:20 +1000 Subject: [PATCH 027/337] tests/multi_bluetooth: Make ble_gap_connect robust against event timing. --- tests/multi_bluetooth/ble_gap_connect.py | 61 ++++++++++++-------- tests/multi_bluetooth/ble_gap_connect.py.exp | 1 + 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/tests/multi_bluetooth/ble_gap_connect.py b/tests/multi_bluetooth/ble_gap_connect.py index ba9c28230d..8b40a29163 100644 --- a/tests/multi_bluetooth/ble_gap_connect.py +++ b/tests/multi_bluetooth/ble_gap_connect.py @@ -10,35 +10,36 @@ _IRQ_CENTRAL_DISCONNECT = const(2) _IRQ_PERIPHERAL_CONNECT = const(7) _IRQ_PERIPHERAL_DISCONNECT = const(8) -waiting_event = None -waiting_data = None +central_connected = False +central_disconnected = False +peripheral_connected = False +peripheral_disconnected = False +conn_handle = None def irq(event, data): - global waiting_event, waiting_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] 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] elif event == _IRQ_PERIPHERAL_DISCONNECT: print("_IRQ_PERIPHERAL_DISCONNECT") - - if waiting_event is not None: - if event == waiting_event: - waiting_event = None - waiting_data = data + peripheral_disconnected = True + remote_addr = data[0] -def wait_for_event(event, timeout_ms): - global waiting_event, waiting_data - waiting_event = event - waiting_data = None - +def wait_for(fn, timeout_ms): t0 = time.ticks_ms() while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: - if waiting_data: + if fn(): return True machine.idle() return False @@ -46,25 +47,31 @@ def wait_for_event(event, timeout_ms): # 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_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS): + if not wait_for(lambda: central_connected, TIMEOUT_MS): return - if not wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS): + if not wait_for(lambda: central_disconnected, TIMEOUT_MS): return + central_connected = False + central_disconnected = False + # 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_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS): + if not wait_for(lambda: central_connected, TIMEOUT_MS): return - print("gap_disconnect:", ble.gap_disconnect(waiting_data[0])) - if not wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS): + print("gap_disconnect:", ble.gap_disconnect(conn_handle)) + if not wait_for(lambda: central_disconnected, TIMEOUT_MS): return finally: ble.active(0) @@ -72,26 +79,32 @@ def instance0(): # 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_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): + if not wait_for(lambda: peripheral_connected, TIMEOUT_MS): return - print("gap_disconnect:", ble.gap_disconnect(waiting_data[0])) - if not wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, 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) # Connect to peripheral and then let the peripheral disconnect us. print("gap_connect") ble.gap_connect(*BDADDR) - if not wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS): + if not wait_for(lambda: peripheral_connected, TIMEOUT_MS): + return + if not wait_for(lambda: peripheral_disconnected, TIMEOUT_MS): return - wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS) finally: ble.active(0) diff --git a/tests/multi_bluetooth/ble_gap_connect.py.exp b/tests/multi_bluetooth/ble_gap_connect.py.exp index 1c96f1d48a..d0dc020703 100644 --- a/tests/multi_bluetooth/ble_gap_connect.py.exp +++ b/tests/multi_bluetooth/ble_gap_connect.py.exp @@ -2,6 +2,7 @@ gap_advertise _IRQ_CENTRAL_CONNECT _IRQ_CENTRAL_DISCONNECT +gap_advertise _IRQ_CENTRAL_CONNECT gap_disconnect: True _IRQ_CENTRAL_DISCONNECT From 01f2d776141d94b8d15d91aa1e0f0cef4244d5f7 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Mon, 31 Aug 2020 15:55:09 +1000 Subject: [PATCH 028/337] stm32/rfcore: Fix length matching in HCI parser. --- ports/stm32/rfcore.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ports/stm32/rfcore.c b/ports/stm32/rfcore.c index 3565c03898..99bb405630 100644 --- a/ports/stm32/rfcore.c +++ b/ports/stm32/rfcore.c @@ -258,11 +258,13 @@ STATIC int ipcc_wait_msg(unsigned int ch, uint32_t timeout_ms) { STATIC void tl_parse_hci_msg(const uint8_t *buf, parse_hci_info_t *parse) { const char *kind; - size_t len = 3 + buf[2]; + size_t len = 0; switch (buf[0]) { case 0x02: { // Standard BT HCI ACL packet kind = "HCI_ACL"; + // + len = 5 + buf[3] + (buf[4] << 8); if (parse != NULL) { parse->cb_fun(parse->cb_env, buf, len); } @@ -271,6 +273,8 @@ STATIC void tl_parse_hci_msg(const uint8_t *buf, parse_hci_info_t *parse) { case 0x04: { // Standard BT HCI event packet kind = "HCI_EVT"; + // + len = 3 + buf[2]; if (parse != NULL) { bool fix = false; if (buf[1] == 0x0e && len == 7 && buf[3] == 0x01 && buf[4] == 0x63 && buf[5] == 0x0c && buf[6] == 0x01) { From 8b4ebd716613ba981c6714510a7adf5c860d7bdf Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 1 Sep 2020 14:13:44 +1000 Subject: [PATCH 029/337] stm32/rfcore: Refactor some helper funcs, and remove some magic numbers. Also explain what the payload fixup code is doing. --- ports/stm32/rfcore.c | 262 +++++++++++++++++++++++++------------------ 1 file changed, 153 insertions(+), 109 deletions(-) diff --git a/ports/stm32/rfcore.c b/ports/stm32/rfcore.c index 99bb405630..5630ea7132 100644 --- a/ports/stm32/rfcore.c +++ b/ports/stm32/rfcore.c @@ -42,18 +42,33 @@ // Define to 1 to print traces of HCI packets #define HCI_TRACE (0) -#define IPCC_CH_BLE (0x01) // BLE HCI command and response -#define IPCC_CH_SYS (0x02) // system HCI command and response -#define IPCC_CH_MM (0x08) // release buffer -#define IPCC_CH_HCI_ACL (0x20) // HCI ACL outgoing data +#define IPCC_CH_BLE (LL_IPCC_CHANNEL_1) // BLE HCI command and response +#define IPCC_CH_SYS (LL_IPCC_CHANNEL_2) // system HCI command and response +#define IPCC_CH_MM (LL_IPCC_CHANNEL_4) // release buffer +#define IPCC_CH_HCI_ACL (LL_IPCC_CHANNEL_6) // HCI ACL outgoing data -#define OGF_VENDOR (0x3f) -#define OCF_WRITE_CONFIG (0x0c) -#define OCF_SET_TX_POWER (0x0f) -#define OCF_BLE_INIT (0x66) +#define OGF_CTLR_BASEBAND (0x03) +#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 HCI_OPCODE(ogf, ocf) ((ogf) << 10 | (ocf)) +#define HCI_KIND_BT_CMD (0x01) // ...? +#define HCI_KIND_BT_ACL (0x02) // +#define HCI_KIND_BT_EVENT (0x04) // +#define HCI_KIND_VENDOR_RESPONSE (0x11) +#define HCI_KIND_VENDOR_EVENT (0x12) + +#define HCI_EVENT_COMMAND_COMPLETE (0x0E) // + +#define SYS_ACK_TIMEOUT_MS (250) +#define BLE_ACK_TIMEOUT_MS (250) + typedef struct _tl_list_node_t { volatile struct _tl_list_node_t *next; volatile struct _tl_list_node_t *prev; @@ -194,12 +209,6 @@ void ipcc_init(uint32_t irq_pri) { // Start IPCC peripheral __HAL_RCC_IPCC_CLK_ENABLE(); - // Enable wanted IRQs - IPCC->C1CR = 0; // IPCC_C1CR_RXOIE; - IPCC->C1MR = 0xffffffff; - NVIC_SetPriority(IPCC_C1_RX_IRQn, irq_pri); - HAL_NVIC_EnableIRQ(IPCC_C1_RX_IRQn); - // Device info table will be populated by FUS/WS on CPU2 boot. // Populate system table @@ -225,127 +234,136 @@ void ipcc_init(uint32_t irq_pri) { ipcc_mem_ble_tab.phci_acl_data_buffer = ipcc_membuf_ble_hci_acl_data_buf; } -STATIC int ipcc_wait_ack(unsigned int ch, uint32_t timeout_ms) { - DEBUG_printf("ipcc_wait_ack\n"); - - uint32_t t0 = mp_hal_ticks_ms(); - while (IPCC->C1TOC2SR & ch) { - if (mp_hal_ticks_ms() - t0 > timeout_ms) { - printf("ipcc_wait_ack: timeout\n"); - return -MP_ETIMEDOUT; - } - } - // C2 cleared IPCC flag - return 0; -} - -STATIC int ipcc_wait_msg(unsigned int ch, uint32_t timeout_ms) { - DEBUG_printf("ipcc_wait_msg\n"); - - uint32_t t0 = mp_hal_ticks_ms(); - while (!(IPCC->C2TOC1SR & ch)) { - if (mp_hal_ticks_ms() - t0 > timeout_ms) { - printf("ipcc_wait_msg: timeout\n"); - return -MP_ETIMEDOUT; - } - } - // C2 set IPCC flag - return 0; -} - /******************************************************************************/ // Transport layer HCI interface STATIC void tl_parse_hci_msg(const uint8_t *buf, parse_hci_info_t *parse) { - const char *kind; + const char *info; size_t len = 0; + bool applied_set_event_event_mask2_fix = false; switch (buf[0]) { - case 0x02: { - // Standard BT HCI ACL packet - kind = "HCI_ACL"; - // + case HCI_KIND_BT_ACL: { + info = "HCI_ACL"; + len = 5 + buf[3] + (buf[4] << 8); if (parse != NULL) { parse->cb_fun(parse->cb_env, buf, len); } break; } - case 0x04: { - // Standard BT HCI event packet - kind = "HCI_EVT"; - // + case HCI_KIND_BT_EVENT: { + info = "HCI_EVT"; + len = 3 + buf[2]; if (parse != NULL) { - bool fix = false; - if (buf[1] == 0x0e && len == 7 && buf[3] == 0x01 && buf[4] == 0x63 && buf[5] == 0x0c && buf[6] == 0x01) { - len -= 1; - fix = true; + + if (buf[1] == HCI_EVENT_COMMAND_COMPLETE && len == 7) { + uint16_t opcode = (buf[5] << 8) | buf[4]; + 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; + } + + if (opcode == HCI_OPCODE(OGF_CTLR_BASEBAND, OCF_CB_RESET) && status == 0) { + // Controller acknowledged reset command. + // This will trigger setting the MAC address. + parse->was_hci_reset_evt = true; + } } + parse->cb_fun(parse->cb_env, buf, len); - if (fix) { - len += 1; - uint8_t data = 0x00; // success + + 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; } - // Check for successful HCI_Reset event - parse->was_hci_reset_evt = buf[1] == 0x0e && buf[2] == 0x04 && buf[3] == 0x01 - && buf[4] == 0x03 && buf[5] == 0x0c && buf[6] == 0x00; } break; } - case 0x11: { - // Response packet + case HCI_KIND_VENDOR_RESPONSE: { // assert(buf[1] == 0x0e); - kind = "VEND_RESP"; + info = "VEND_RESP"; + len = 3 + buf[2]; // ??? // uint16_t cmd = buf[4] | buf[5] << 8; // uint8_t status = buf[6]; break; } - case 0x12: { - // Event packet + case HCI_KIND_VENDOR_EVENT: { // assert(buf[1] == 0xff); - kind = "VEND_EVT"; + info = "VEND_EVT"; + len = 3 + buf[2]; // ??? // uint16_t evt = buf[3] | buf[4] << 8; break; } default: - kind = "HCI_UNKNOWN"; + info = "HCI_UNKNOWN"; break; } #if HCI_TRACE - printf("[% 8d] %s(%02x", mp_hal_ticks_ms(), kind, buf[0]); + printf("[% 8d] <%s(%02x", mp_hal_ticks_ms(), info, buf[0]); for (int i = 1; i < len; ++i) { printf(":%02x", buf[i]); } - printf(")\n"); + printf(")"); + if (parse && parse->was_hci_reset_evt) { + printf(" (reset)"); + } + if (applied_set_event_event_mask2_fix) { + printf(" (mask2 fix)"); + } + printf("\n"); + #else - (void)kind; + (void)info; #endif } +STATIC void tl_process_msg(volatile tl_list_node_t *head, unsigned int ch, parse_hci_info_t *parse) { + volatile tl_list_node_t *cur = head->next; + bool added_to_free_queue = false; + while (cur != head) { + tl_parse_hci_msg((uint8_t *)cur->body, parse); + + volatile tl_list_node_t *next = tl_list_unlink(cur); + + // If this node is allocated from the memmgr event pool, then place it into the free buffer. + if ((uint8_t *)cur >= ipcc_membuf_memmgr_evt_pool && (uint8_t *)cur < ipcc_membuf_memmgr_evt_pool + sizeof(ipcc_membuf_memmgr_evt_pool)) { + // Place memory back in free pool. + tl_list_append(&ipcc_mem_memmgr_free_buf_queue, cur); + added_to_free_queue = true; + } + + cur = next; + } + + if (added_to_free_queue) { + // Notify change in free pool. + LL_C1_IPCC_SetFlag_CHx(IPCC, IPCC_CH_MM); + } +} + STATIC void tl_check_msg(volatile tl_list_node_t *head, unsigned int ch, parse_hci_info_t *parse) { - if (IPCC->C2TOC1SR & ch) { - // Message available on CH2 - volatile tl_list_node_t *cur = head->next; - bool free = false; - while (cur != head) { - tl_parse_hci_msg((uint8_t *)cur->body, parse); - volatile tl_list_node_t *next = tl_list_unlink(cur); - if ((void *)&ipcc_membuf_memmgr_evt_pool[0] <= (void *)cur - && (void *)cur < (void *)&ipcc_membuf_memmgr_evt_pool[MP_ARRAY_SIZE(ipcc_membuf_memmgr_evt_pool)]) { - // Place memory back in free pool - tl_list_append(&ipcc_mem_memmgr_free_buf_queue, cur); - free = true; - } - cur = next; - } - if (free) { - // Notify change in free pool - IPCC->C1SCR = IPCC_CH_MM << 16; - } - // Clear receive channel - IPCC->C1SCR = ch; + if (LL_C2_IPCC_IsActiveFlag_CHx(IPCC, ch)) { + tl_process_msg(head, ch, parse); + + // Clear receive channel. + LL_C1_IPCC_ClearFlag_CHx(IPCC, ch); + } +} + +STATIC void tl_check_msg_ble(volatile tl_list_node_t *head, parse_hci_info_t *parse) { + if (LL_C2_IPCC_IsActiveFlag_CHx(IPCC, IPCC_CH_BLE)) { + tl_process_msg(head, IPCC_CH_BLE, parse); + + LL_C1_IPCC_ClearFlag_CHx(IPCC, IPCC_CH_BLE); } } @@ -358,25 +376,51 @@ STATIC void tl_hci_cmd(uint8_t *cmd, unsigned int ch, uint8_t hdr, uint16_t opco cmd[10] = opcode >> 8; cmd[11] = len; memcpy(&cmd[12], buf, len); - // IPCC indicate - IPCC->C1SCR = ch << 16; + + // Indicate that this channel is ready. + LL_C1_IPCC_SetFlag_CHx(IPCC, ch); } -STATIC void tl_sys_wait_resp(const uint8_t *buf, unsigned int ch) { - if (ipcc_wait_ack(ch, 250) == 0) { - tl_parse_hci_msg(buf, NULL); +STATIC int tl_sys_wait_ack(const uint8_t *buf) { + uint32_t t0 = mp_hal_ticks_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) { + printf("tl_sys_wait_ack: timeout\n"); + return -MP_ETIMEDOUT; + } } + + // C1-to-C2 bit cleared, so process (but ignore) the response. + tl_parse_hci_msg(buf, NULL); + return 0; } STATIC void tl_sys_hci_cmd_resp(uint16_t opcode, size_t len, const uint8_t *buf) { - tl_hci_cmd((uint8_t *)&ipcc_membuf_sys_cmd_buf, IPCC_CH_SYS, 0x10, opcode, len, buf); - tl_sys_wait_resp((uint8_t *)&ipcc_membuf_sys_cmd_buf, IPCC_CH_SYS); + 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 int tl_ble_wait_resp(void) { + uint32_t t0 = mp_hal_ticks_ms(); + 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; + } + } + + // C2 set IPCC flag. + tl_check_msg_ble(&ipcc_mem_ble_evt_queue, NULL); + return 0; +} + +// 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((uint8_t *)&ipcc_membuf_ble_cmd_buf[0], IPCC_CH_BLE, 0x01, opcode, len, buf); - ipcc_wait_msg(IPCC_CH_BLE, 250); - tl_check_msg(&ipcc_mem_ble_evt_queue, IPCC_CH_BLE, NULL); + tl_hci_cmd(ipcc_membuf_ble_cmd_buf, IPCC_CH_BLE, HCI_KIND_BT_CMD, opcode, len, buf); + tl_ble_wait_resp(); + } /******************************************************************************/ @@ -445,7 +489,7 @@ void rfcore_ble_init(void) { // Clear any outstanding messages from ipcc_init tl_check_msg(&ipcc_mem_sys_queue, IPCC_CH_SYS, NULL); - tl_check_msg(&ipcc_mem_ble_evt_queue, IPCC_CH_BLE, NULL); + 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); @@ -456,7 +500,7 @@ void rfcore_ble_hci_cmd(size_t len, const uint8_t *src) { DEBUG_printf("rfcore_ble_hci_cmd\n"); #if HCI_TRACE - printf("[% 8d] HCI_CMD(%02x", mp_hal_ticks_ms(), src[0]); + printf("[% 8d] >HCI_CMD(%02x", mp_hal_ticks_ms(), src[0]); for (int i = 1; i < len; ++i) { printf(":%02x", src[i]); } @@ -465,10 +509,10 @@ void rfcore_ble_hci_cmd(size_t len, const uint8_t *src) { tl_list_node_t *n; uint32_t ch; - if (src[0] == 0x01) { + if (src[0] == HCI_KIND_BT_CMD) { n = (tl_list_node_t *)&ipcc_membuf_ble_cmd_buf[0]; ch = IPCC_CH_BLE; - } else if (src[0] == 0x02) { + } 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; } else { @@ -480,13 +524,13 @@ void rfcore_ble_hci_cmd(size_t len, const uint8_t *src) { n->prev = n; memcpy(n->body, src, len); - // IPCC indicate - IPCC->C1SCR = ch << 16; + // IPCC indicate. + LL_C1_IPCC_SetFlag_CHx(IPCC, ch); } 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(&ipcc_mem_ble_evt_queue, IPCC_CH_BLE, &parse); + tl_check_msg_ble(&ipcc_mem_ble_evt_queue, &parse); // Intercept HCI_Reset events and reconfigure the controller following the reset if (parse.was_hci_reset_evt) { From e2390d5a2f4f18912878596965b0c794ac153cb4 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 1 Sep 2020 14:13:44 +1000 Subject: [PATCH 030/337] stm32/rfcore: Enable RX IRQ on BLE IPCC channel for better performance. Before this change there was up to a 128ms delay on incoming payloads from CPU2 as it was polled by SysTick. Now the RX IRQ immediately schedules the PendSV. --- ports/stm32/rfcore.c | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/ports/stm32/rfcore.c b/ports/stm32/rfcore.c index 5630ea7132..ca2ad747c4 100644 --- a/ports/stm32/rfcore.c +++ b/ports/stm32/rfcore.c @@ -164,6 +164,9 @@ 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 @@ -209,6 +212,13 @@ void ipcc_init(uint32_t irq_pri) { // Start IPCC peripheral __HAL_RCC_IPCC_CLK_ENABLE(); + // Enable receive IRQ on the BLE channel. + LL_C1_IPCC_EnableIT_RXO(IPCC); + LL_C1_IPCC_DisableReceiveChannel(IPCC, LL_IPCC_CHANNEL_1 | LL_IPCC_CHANNEL_2 | LL_IPCC_CHANNEL_3 | LL_IPCC_CHANNEL_4 | LL_IPCC_CHANNEL_5 | LL_IPCC_CHANNEL_6); + LL_C1_IPCC_EnableReceiveChannel(IPCC, IPCC_CH_BLE); + NVIC_SetPriority(IPCC_C1_RX_IRQn, irq_pri); + HAL_NVIC_EnableIRQ(IPCC_C1_RX_IRQn); + // Device info table will be populated by FUS/WS on CPU2 boot. // Populate system table @@ -360,10 +370,10 @@ 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 (LL_C2_IPCC_IsActiveFlag_CHx(IPCC, IPCC_CH_BLE)) { + if (had_ble_irq) { tl_process_msg(head, IPCC_CH_BLE, parse); - LL_C1_IPCC_ClearFlag_CHx(IPCC, IPCC_CH_BLE); + had_ble_irq = false; } } @@ -404,7 +414,7 @@ STATIC void tl_sys_hci_cmd_resp(uint16_t opcode, size_t len, const uint8_t *buf) STATIC int tl_ble_wait_resp(void) { uint32_t t0 = mp_hal_ticks_ms(); - while (!LL_C2_IPCC_IsActiveFlag_CHx(IPCC, IPCC_CH_BLE)) { + while (!had_ble_irq) { if (mp_hal_ticks_ms() - t0 > BLE_ACK_TIMEOUT_MS) { printf("tl_ble_wait_resp: timeout\n"); return -MP_ETIMEDOUT; @@ -553,4 +563,26 @@ void rfcore_ble_set_txpower(uint8_t level) { tl_ble_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_SET_TX_POWER), 2, buf); } +// IPCC IRQ Handlers +void IPCC_C1_TX_IRQHandler(void) { + IRQ_ENTER(IPCC_C1_TX_IRQn); + IRQ_EXIT(IPCC_C1_TX_IRQn); +} + +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; + + 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); + } + + IRQ_EXIT(IPCC_C1_RX_IRQn); +} + #endif // defined(STM32WB) From 632e3b7acc51b3b28f310ef5e23b0983c8e8b700 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 28 Aug 2020 15:51:15 +1000 Subject: [PATCH 031/337] stm32/boards/NUCLEO_WB55: Add Python helper code for rfcore. This allows prototyping rfcore.c improvements from Python. This was mostly written by @dpgeorge with small modifications to work after rfcore_init() by @jimmo. --- ports/stm32/boards/NUCLEO_WB55/rfcore.py | 347 +++++++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 ports/stm32/boards/NUCLEO_WB55/rfcore.py diff --git a/ports/stm32/boards/NUCLEO_WB55/rfcore.py b/ports/stm32/boards/NUCLEO_WB55/rfcore.py new file mode 100644 index 0000000000..e612499a78 --- /dev/null +++ b/ports/stm32/boards/NUCLEO_WB55/rfcore.py @@ -0,0 +1,347 @@ +# 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 some helpers to allow Python code to access the IPCC +# 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. + +from machine import mem8, mem16, mem32 +import time, struct, uctypes +import stm + + +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 = uctypes.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 +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) + + +@micropython.asm_thumb +def asm_sev_wfe(): + data(2, 0xBF40) # sev + data(2, 0xBF20) # wfe + + +TABLE_DEVICE_INFO = const(0) +TABLE_BLE = const(1) +TABLE_SYS = const(3) +TABLE_MEM_MANAGER = const(4) + +CHANNEL_BLE = const(1) +CHANNEL_SYS = const(2) +CHANNEL_TRACES = const(4) +CHANNEL_ACL = const(6) + +INDICATOR_HCI_COMMAND = const(0x01) +INDICATOR_HCI_EVENT = const(0x04) +INDICATOR_FUS_COMMAND = const(0x10) +INDICATOR_FUS_RESPONSE = const(0x11) +INDICATOR_FUS_EVENT = const(0x12) + +MAGIC_FUS_ACTIVE = const(0xA94656B9) + + +def get_ipccdba(): + return mem32[stm.FLASH + stm.FLASH_IPCCBR] & 0x3FFF + + +def get_ipcc_table(table): + return mem32[SRAM2A_BASE + get_ipccdba() + table * 4] + + +def get_ipcc_table_word(table, offset): + return mem32[get_ipcc_table(table) + offset * 4] & 0xFFFFFFFF + + +def get_ipcc_table_byte(table, offset): + return mem8[get_ipcc_table(table) + offset] & 0xFF + + +def sram2a_dump(num_words=64, width=8): + print("SRAM2A @%08x" % SRAM2A_BASE) + for i in range((num_words + width - 1) // width): + print(" %04x " % (i * 4 * width), end="") + for j in range(width): + print(" %08x" % (mem32[SRAM2A_BASE + (i * width + j) * 4] & 0xFFFFFFFF), end="") + print() + + +SYS_CMD_BUF = 0 # next*,prev*,type8,...; 272 bytes +SYS_SYS_QUEUE = 0 # next*,prev* + +MM_BLE_SPARE_EVT_BUF = 0 # next*,prev*; 272 bytes +MM_SYS_SPARE_EVT_BUF = 0 # next*,prev*; 272 bytes +MM_BLE_POOL = 0 # ? +MM_BLE_POOL_SIZE = 0 # ? +MM_FREE_BUF_QUEUE = 0 # next*,prev* +MM_EV_POOL = 0 # ? +MM_EV_POOL_SIZE = 0 # ? + +BLE_CMD_BUF = 0 +BLE_CS_BUF = 0 +BLE_EVT_QUEUE = 0 +BLE_HCI_ACL_DATA_BUF = 0 + + +def ipcc_init(): + global SYS_CMD_BUF, SYS_SYS_QUEUE + SYS_CMD_BUF = get_ipcc_table_word(TABLE_SYS, 0) + SYS_SYS_QUEUE = get_ipcc_table_word(TABLE_SYS, 1) + + global MM_BLE_SPARE_EVT_BUF, MM_SYS_SPARE_EVT_BUF, MM_BLE_POOL, MM_BLE_POOL_SIZE, MM_FREE_BUF_QUEUE, MM_EV_POOL, MM_EV_POOL_SIZE + MM_BLE_SPARE_EVT_BUF = get_ipcc_table_word(TABLE_MEM_MANAGER, 0) + MM_SYS_SPARE_EVT_BUF = get_ipcc_table_word(TABLE_MEM_MANAGER, 1) + MM_BLE_POOL = get_ipcc_table_word(TABLE_MEM_MANAGER, 2) + MM_BLE_POOL_SIZE = get_ipcc_table_word(TABLE_MEM_MANAGER, 3) + MM_FREE_BUF_QUEUE = get_ipcc_table_word(TABLE_MEM_MANAGER, 4) + MM_EV_POOL = get_ipcc_table_word(TABLE_MEM_MANAGER, 5) + MM_EV_POOL_SIZE = get_ipcc_table_word(TABLE_MEM_MANAGER, 6) + + global BLE_CMD_BUF, BLE_CS_BUF, BLE_EVT_QUEUE, BLE_HCI_ACL_DATA_BUF + BLE_CMD_BUF = get_ipcc_table_word(TABLE_BLE, 0) + BLE_CS_BUF = get_ipcc_table_word(TABLE_BLE, 1) + BLE_EVT_QUEUE = get_ipcc_table_word(TABLE_BLE, 2) + BLE_HCI_ACL_DATA_BUF = get_ipcc_table_word(TABLE_BLE, 3) + + print("IPCC initialised") + print("SYS: 0x%08x 0x%08x" % (SYS_CMD_BUF, SYS_SYS_QUEUE)) + 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 + + +def info(): + sfr = mem32[stm.FLASH + stm.FLASH_SFR] + srrvr = mem32[stm.FLASH + stm.FLASH_SRRVR] + + print("IPCCDBA : 0x%08x" % (get_ipccdba() & 0x3FFF)) + print("DDS : %r" % bool(sfr & (1 << 12))) + print("FSD : %r" % bool(sfr & (1 << 8))) + print("SFSA : 0x%08x" % (sfr & 0xFF)) + print("C2OPT : %r" % bool(srrvr & (1 << 31))) + print("NBRSD : %r" % bool(srrvr & (1 << 30))) + print("SNBRSA : 0x%08x" % ((srrvr >> 25) & 0x1F)) + print("BRSD : %r" % bool(srrvr & (1 << 23))) + print("SBRSA : 0x%08x" % ((srrvr >> 18) & 0x1F)) + print("SBRV : 0x%08x" % (srrvr & 0x3FFFF)) + + +def dev_info(): + def dump_version(offset): + x = get_ipcc_table_word(TABLE_DEVICE_INFO, offset) + print( + "0x%08x (%u.%u.%u.%u.%u)" + % (x, x >> 24, x >> 16 & 0xFF, x >> 8 & 0xFF, x >> 4 & 0xF, x & 0xF) + ) + + def dump_memory_size(offset): + x = get_ipcc_table_word(TABLE_DEVICE_INFO, offset) + print( + "0x%08x (SRAM2b=%uk SRAM2a=%uk flash=%uk)" + % (x, x >> 24, x >> 16 & 0xFF, (x & 0xFF) * 4) + ) + + print("Device information table @%08x:" % get_ipcc_table(TABLE_DEVICE_INFO)) + if fus_active(): + # layout when running FUS + print("FUS is active") + print("state : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 0)) + print("last FUS active state : 0x%02x" % get_ipcc_table_byte(TABLE_DEVICE_INFO, 5)) + print("last wireless stack state: 0x%02x" % get_ipcc_table_byte(TABLE_DEVICE_INFO, 6)) + print("cur wireless stack type : 0x%02x" % get_ipcc_table_byte(TABLE_DEVICE_INFO, 7)) + print("safe boot version : ", end="") + dump_version(2) + print("FUS version : ", end="") + dump_version(3) + print("FUS memory size : ", end="") + dump_memory_size(4) + print("wireless stack version : ", end="") + dump_version(5) + print("wireless stack mem size : ", end="") + dump_memory_size(6) + print("wireless FW-BLE info : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 7)) + print("wireless FW-thread info : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 8)) + print( + "UID64 : 0x%08x 0x%08x" + % ( + get_ipcc_table_word(TABLE_DEVICE_INFO, 9), + get_ipcc_table_word(TABLE_DEVICE_INFO, 10), + ) + ) + print("device ID : 0x%04x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 11)) + else: + # layout when running WS + print("WS is active") + print("safe boot version : ", end="") + dump_version(0) + print("FUS version : ", end="") + dump_version(1) + print("FUS memory size : ", end="") + dump_memory_size(2) + print("FUS info : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 3)) + print("wireless stack version : ", end="") + dump_version(4) + print("wireless stack mem size : ", end="") + dump_memory_size(5) + print("wireless stack info : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 7)) + print("wireless reserved : 0x%08x" % get_ipcc_table_word(TABLE_DEVICE_INFO, 7)) + + +def ipcc_state(): + print("IPCC:") + print(" C1CR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C1CR] & 0xFFFFFFFF), end="") + print(" C2CR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C2CR] & 0xFFFFFFFF)) + print(" C1MR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C1MR] & 0xFFFFFFFF), end="") + print(" C2MR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C2MR] & 0xFFFFFFFF)) + # these always read 0 + # print(' C1SCR: 0x%08x' % (mem32[stm.IPCC + stm.IPCC_C1SCR] & 0xffffffff), end='') + # print(' C2SCR: 0x%08x' % (mem32[stm.IPCC + stm.IPCC_C2SCR] & 0xffffffff)) + print(" C1TOC2SR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C1TOC2SR] & 0xFFFFFFFF), end="") + print(" C2TOC1SR: 0x%08x" % (mem32[stm.IPCC + stm.IPCC_C2TOC1SR] & 0xFFFFFFFF)) + + +sram2a_dump(264) +ipcc_init() +info() +dev_info() From 5f50568b1f7ab4f917b8d782d643ac55612910dd Mon Sep 17 00:00:00 2001 From: Albort Xue Date: Tue, 7 Apr 2020 15:54:01 +0800 Subject: [PATCH 032/337] mimxrt/boards: Add MIMXRT1064_EVK board. --- ports/mimxrt/Makefile | 2 +- ports/mimxrt/boards/MIMXRT1064.ld | 8 + .../evkmimxrt1064_flexspi_nor_config.h | 268 ++++++++++++++++++ .../boards/MIMXRT1064_EVK/flash_config.c | 49 ++++ .../boards/MIMXRT1064_EVK/mpconfigboard.h | 8 + .../boards/MIMXRT1064_EVK/mpconfigboard.mk | 9 + ports/mimxrt/boards/MIMXRT1064_EVK/pins.c | 33 +++ ports/mimxrt/boards/MIMXRT1064_EVK/pins.h | 30 ++ 8 files changed, 406 insertions(+), 1 deletion(-) create mode 100644 ports/mimxrt/boards/MIMXRT1064.ld create mode 100644 ports/mimxrt/boards/MIMXRT1064_EVK/evkmimxrt1064_flexspi_nor_config.h create mode 100644 ports/mimxrt/boards/MIMXRT1064_EVK/flash_config.c create mode 100644 ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.h create mode 100644 ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.mk create mode 100644 ports/mimxrt/boards/MIMXRT1064_EVK/pins.c create mode 100644 ports/mimxrt/boards/MIMXRT1064_EVK/pins.h diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index c4ea70128e..85e77b978e 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -40,7 +40,7 @@ INC += -I$(TOP)/lib/tinyusb/hw INC += -I$(TOP)/lib/tinyusb/hw/bsp/teensy_40 CFLAGS_MCU = -mtune=cortex-m7 -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 -CFLAGS = $(INC) -Wall -Werror -Wdouble-promotion -Wfloat-conversion -std=c99 -nostdlib -mthumb $(CFLAGS_MCU) +CFLAGS += $(INC) -Wall -Werror -Wdouble-promotion -Wfloat-conversion -std=c99 -nostdlib -mthumb $(CFLAGS_MCU) CFLAGS += -DCPU_$(MCU_SERIES) -DCPU_$(MCU_VARIANT) CFLAGS += -DXIP_EXTERNAL_FLASH=1 \ -DXIP_BOOT_HEADER_ENABLE=1 \ diff --git a/ports/mimxrt/boards/MIMXRT1064.ld b/ports/mimxrt/boards/MIMXRT1064.ld new file mode 100644 index 0000000000..bf37c21802 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1064.ld @@ -0,0 +1,8 @@ +/* 24kiB stack. */ +__stack_size__ = 0x6000; +_estack = __StackTop; +_sstack = __StackLimit; + +/* Use second OCRAM bank for GC heap. */ +_gc_heap_start = ORIGIN(m_data2); +_gc_heap_end = ORIGIN(m_data2) + LENGTH(m_data2); \ No newline at end of file diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/evkmimxrt1064_flexspi_nor_config.h b/ports/mimxrt/boards/MIMXRT1064_EVK/evkmimxrt1064_flexspi_nor_config.h new file mode 100644 index 0000000000..efdfe583fb --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1064_EVK/evkmimxrt1064_flexspi_nor_config.h @@ -0,0 +1,268 @@ +/* + * Copyright 2018 NXP + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __EVKMIMXRT1064_FLEXSPI_NOR_CONFIG__ +#define __EVKMIMXRT1064_FLEXSPI_NOR_CONFIG__ + +#include +#include +#include "fsl_common.h" + +/*! @name Driver version */ +/*@{*/ +/*! @brief XIP_BOARD driver version 2.0.0. */ +#define FSL_XIP_BOARD_DRIVER_VERSION (MAKE_VERSION(2, 0, 0)) +/*@}*/ + +/* FLEXSPI memory config block related defintions */ +#define FLEXSPI_CFG_BLK_TAG (0x42464346UL) // ascii "FCFB" Big Endian +#define FLEXSPI_CFG_BLK_VERSION (0x56010400UL) // V1.4.0 +#define FLEXSPI_CFG_BLK_SIZE (512) + +/* FLEXSPI Feature related definitions */ +#define FLEXSPI_FEATURE_HAS_PARALLEL_MODE 1 + +/* Lookup table related defintions */ +#define CMD_INDEX_READ 0 +#define CMD_INDEX_READSTATUS 1 +#define CMD_INDEX_WRITEENABLE 2 +#define CMD_INDEX_WRITE 4 + +#define CMD_LUT_SEQ_IDX_READ 0 +#define CMD_LUT_SEQ_IDX_READSTATUS 1 +#define CMD_LUT_SEQ_IDX_WRITEENABLE 3 +#define CMD_LUT_SEQ_IDX_WRITE 9 + +#define CMD_SDR 0x01 +#define CMD_DDR 0x21 +#define RADDR_SDR 0x02 +#define RADDR_DDR 0x22 +#define CADDR_SDR 0x03 +#define CADDR_DDR 0x23 +#define MODE1_SDR 0x04 +#define MODE1_DDR 0x24 +#define MODE2_SDR 0x05 +#define MODE2_DDR 0x25 +#define MODE4_SDR 0x06 +#define MODE4_DDR 0x26 +#define MODE8_SDR 0x07 +#define MODE8_DDR 0x27 +#define WRITE_SDR 0x08 +#define WRITE_DDR 0x28 +#define READ_SDR 0x09 +#define READ_DDR 0x29 +#define LEARN_SDR 0x0A +#define LEARN_DDR 0x2A +#define DATSZ_SDR 0x0B +#define DATSZ_DDR 0x2B +#define DUMMY_SDR 0x0C +#define DUMMY_DDR 0x2C +#define DUMMY_RWDS_SDR 0x0D +#define DUMMY_RWDS_DDR 0x2D +#define JMP_ON_CS 0x1F +#define STOP 0 + +#define FLEXSPI_1PAD 0 +#define FLEXSPI_2PAD 1 +#define FLEXSPI_4PAD 2 +#define FLEXSPI_8PAD 3 + +#define FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1) \ + (FLEXSPI_LUT_OPERAND0(op0) | FLEXSPI_LUT_NUM_PADS0(pad0) | FLEXSPI_LUT_OPCODE0(cmd0) | FLEXSPI_LUT_OPERAND1(op1) | \ + FLEXSPI_LUT_NUM_PADS1(pad1) | FLEXSPI_LUT_OPCODE1(cmd1)) + +//!@brief Definitions for FlexSPI Serial Clock Frequency +typedef enum _FlexSpiSerialClockFreq +{ + kFlexSpiSerialClk_30MHz = 1, + kFlexSpiSerialClk_50MHz = 2, + kFlexSpiSerialClk_60MHz = 3, + kFlexSpiSerialClk_75MHz = 4, + kFlexSpiSerialClk_80MHz = 5, + kFlexSpiSerialClk_100MHz = 6, + kFlexSpiSerialClk_120MHz = 7, + kFlexSpiSerialClk_133MHz = 8, + kFlexSpiSerialClk_166MHz = 9, +} flexspi_serial_clk_freq_t; + +//!@brief FlexSPI clock configuration type +enum +{ + kFlexSpiClk_SDR, //!< Clock configure for SDR mode + kFlexSpiClk_DDR, //!< Clock configurat for DDR mode +}; + +//!@brief FlexSPI Read Sample Clock Source definition +typedef enum _FlashReadSampleClkSource +{ + kFlexSPIReadSampleClk_LoopbackInternally = 0, + kFlexSPIReadSampleClk_LoopbackFromDqsPad = 1, + kFlexSPIReadSampleClk_LoopbackFromSckPad = 2, + kFlexSPIReadSampleClk_ExternalInputFromDqsPad = 3, +} flexspi_read_sample_clk_t; + +//!@brief Misc feature bit definitions +enum +{ + kFlexSpiMiscOffset_DiffClkEnable = 0, //!< Bit for Differential clock enable + kFlexSpiMiscOffset_Ck2Enable = 1, //!< Bit for CK2 enable + kFlexSpiMiscOffset_ParallelEnable = 2, //!< Bit for Parallel mode enable + kFlexSpiMiscOffset_WordAddressableEnable = 3, //!< Bit for Word Addressable enable + kFlexSpiMiscOffset_SafeConfigFreqEnable = 4, //!< Bit for Safe Configuration Frequency enable + kFlexSpiMiscOffset_PadSettingOverrideEnable = 5, //!< Bit for Pad setting override enable + kFlexSpiMiscOffset_DdrModeEnable = 6, //!< Bit for DDR clock confiuration indication. +}; + +//!@brief Flash Type Definition +enum +{ + kFlexSpiDeviceType_SerialNOR = 1, //!< Flash devices are Serial NOR + kFlexSpiDeviceType_SerialNAND = 2, //!< Flash devices are Serial NAND + kFlexSpiDeviceType_SerialRAM = 3, //!< Flash devices are Serial RAM/HyperFLASH + kFlexSpiDeviceType_MCP_NOR_NAND = 0x12, //!< Flash device is MCP device, A1 is Serial NOR, A2 is Serial NAND + kFlexSpiDeviceType_MCP_NOR_RAM = 0x13, //!< Flash deivce is MCP device, A1 is Serial NOR, A2 is Serial RAMs +}; + +//!@brief Flash Pad Definitions +enum +{ + kSerialFlash_1Pad = 1, + kSerialFlash_2Pads = 2, + kSerialFlash_4Pads = 4, + kSerialFlash_8Pads = 8, +}; + +//!@brief FlexSPI LUT Sequence structure +typedef struct _lut_sequence +{ + uint8_t seqNum; //!< Sequence Number, valid number: 1-16 + uint8_t seqId; //!< Sequence Index, valid number: 0-15 + uint16_t reserved; +} flexspi_lut_seq_t; + +//!@brief Flash Configuration Command Type +enum +{ + kDeviceConfigCmdType_Generic, //!< Generic command, for example: configure dummy cycles, drive strength, etc + kDeviceConfigCmdType_QuadEnable, //!< Quad Enable command + kDeviceConfigCmdType_Spi2Xpi, //!< Switch from SPI to DPI/QPI/OPI mode + kDeviceConfigCmdType_Xpi2Spi, //!< Switch from DPI/QPI/OPI to SPI mode + kDeviceConfigCmdType_Spi2NoCmd, //!< Switch to 0-4-4/0-8-8 mode + kDeviceConfigCmdType_Reset, //!< Reset device command +}; + +//!@brief FlexSPI Memory Configuration Block +typedef struct _FlexSPIConfig +{ + uint32_t tag; //!< [0x000-0x003] Tag, fixed value 0x42464346UL + uint32_t version; //!< [0x004-0x007] Version,[31:24] -'V', [23:16] - Major, [15:8] - Minor, [7:0] - bugfix + uint32_t reserved0; //!< [0x008-0x00b] Reserved for future use + uint8_t readSampleClkSrc; //!< [0x00c-0x00c] Read Sample Clock Source, valid value: 0/1/3 + uint8_t csHoldTime; //!< [0x00d-0x00d] CS hold time, default value: 3 + uint8_t csSetupTime; //!< [0x00e-0x00e] CS setup time, default value: 3 + uint8_t columnAddressWidth; //!< [0x00f-0x00f] Column Address with, for HyperBus protocol, it is fixed to 3, For + //! Serial NAND, need to refer to datasheet + uint8_t deviceModeCfgEnable; //!< [0x010-0x010] Device Mode Configure enable flag, 1 - Enable, 0 - Disable + uint8_t deviceModeType; //!< [0x011-0x011] Specify the configuration command type:Quad Enable, DPI/QPI/OPI switch, + //! Generic configuration, etc. + uint16_t waitTimeCfgCommands; //!< [0x012-0x013] Wait time for all configuration commands, unit: 100us, Used for + //! DPI/QPI/OPI switch or reset command + flexspi_lut_seq_t deviceModeSeq; //!< [0x014-0x017] Device mode sequence info, [7:0] - LUT sequence id, [15:8] - LUt + //! sequence number, [31:16] Reserved + uint32_t deviceModeArg; //!< [0x018-0x01b] Argument/Parameter for device configuration + uint8_t configCmdEnable; //!< [0x01c-0x01c] Configure command Enable Flag, 1 - Enable, 0 - Disable + uint8_t configModeType[3]; //!< [0x01d-0x01f] Configure Mode Type, similar as deviceModeTpe + flexspi_lut_seq_t + configCmdSeqs[3]; //!< [0x020-0x02b] Sequence info for Device Configuration command, similar as deviceModeSeq + uint32_t reserved1; //!< [0x02c-0x02f] Reserved for future use + uint32_t configCmdArgs[3]; //!< [0x030-0x03b] Arguments/Parameters for device Configuration commands + uint32_t reserved2; //!< [0x03c-0x03f] Reserved for future use + uint32_t controllerMiscOption; //!< [0x040-0x043] Controller Misc Options, see Misc feature bit definitions for more + //! details + uint8_t deviceType; //!< [0x044-0x044] Device Type: See Flash Type Definition for more details + uint8_t sflashPadType; //!< [0x045-0x045] Serial Flash Pad Type: 1 - Single, 2 - Dual, 4 - Quad, 8 - Octal + uint8_t serialClkFreq; //!< [0x046-0x046] Serial Flash Frequencey, device specific definitions, See System Boot + //! Chapter for more details + uint8_t lutCustomSeqEnable; //!< [0x047-0x047] LUT customization Enable, it is required if the program/erase cannot + //! be done using 1 LUT sequence, currently, only applicable to HyperFLASH + uint32_t reserved3[2]; //!< [0x048-0x04f] Reserved for future use + uint32_t sflashA1Size; //!< [0x050-0x053] Size of Flash connected to A1 + uint32_t sflashA2Size; //!< [0x054-0x057] Size of Flash connected to A2 + uint32_t sflashB1Size; //!< [0x058-0x05b] Size of Flash connected to B1 + uint32_t sflashB2Size; //!< [0x05c-0x05f] Size of Flash connected to B2 + uint32_t csPadSettingOverride; //!< [0x060-0x063] CS pad setting override value + uint32_t sclkPadSettingOverride; //!< [0x064-0x067] SCK pad setting override value + uint32_t dataPadSettingOverride; //!< [0x068-0x06b] data pad setting override value + uint32_t dqsPadSettingOverride; //!< [0x06c-0x06f] DQS pad setting override value + uint32_t timeoutInMs; //!< [0x070-0x073] Timeout threshold for read status command + uint32_t commandInterval; //!< [0x074-0x077] CS deselect interval between two commands + uint16_t dataValidTime[2]; //!< [0x078-0x07b] CLK edge to data valid time for PORT A and PORT B, in terms of 0.1ns + uint16_t busyOffset; //!< [0x07c-0x07d] Busy offset, valid value: 0-31 + uint16_t busyBitPolarity; //!< [0x07e-0x07f] Busy flag polarity, 0 - busy flag is 1 when flash device is busy, 1 - + //! busy flag is 0 when flash device is busy + uint32_t lookupTable[64]; //!< [0x080-0x17f] Lookup table holds Flash command sequences + flexspi_lut_seq_t lutCustomSeq[12]; //!< [0x180-0x1af] Customizable LUT Sequences + uint32_t reserved4[4]; //!< [0x1b0-0x1bf] Reserved for future use +} flexspi_mem_config_t; + +/* */ +#define NOR_CMD_INDEX_READ CMD_INDEX_READ //!< 0 +#define NOR_CMD_INDEX_READSTATUS CMD_INDEX_READSTATUS //!< 1 +#define NOR_CMD_INDEX_WRITEENABLE CMD_INDEX_WRITEENABLE //!< 2 +#define NOR_CMD_INDEX_ERASESECTOR 3 //!< 3 +#define NOR_CMD_INDEX_PAGEPROGRAM CMD_INDEX_WRITE //!< 4 +#define NOR_CMD_INDEX_CHIPERASE 5 //!< 5 +#define NOR_CMD_INDEX_DUMMY 6 //!< 6 +#define NOR_CMD_INDEX_ERASEBLOCK 7 //!< 7 + +#define NOR_CMD_LUT_SEQ_IDX_READ CMD_LUT_SEQ_IDX_READ //!< 0 READ LUT sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_READSTATUS \ + CMD_LUT_SEQ_IDX_READSTATUS //!< 1 Read Status LUT sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_READSTATUS_XPI \ + 2 //!< 2 Read status DPI/QPI/OPI sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE \ + CMD_LUT_SEQ_IDX_WRITEENABLE //!< 3 Write Enable sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE_XPI \ + 4 //!< 4 Write Enable DPI/QPI/OPI sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_ERASESECTOR 5 //!< 5 Erase Sector sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_ERASEBLOCK 8 //!< 8 Erase Block sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM \ + CMD_LUT_SEQ_IDX_WRITE //!< 9 Program sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_CHIPERASE 11 //!< 11 Chip Erase sequence in lookupTable id stored in config block +#define NOR_CMD_LUT_SEQ_IDX_READ_SFDP 13 //!< 13 Read SFDP sequence in lookupTable id stored in config block +#define NOR_CMD_LUT_SEQ_IDX_RESTORE_NOCMD \ + 14 //!< 14 Restore 0-4-4/0-8-8 mode sequence id in lookupTable stored in config block +#define NOR_CMD_LUT_SEQ_IDX_EXIT_NOCMD \ + 15 //!< 15 Exit 0-4-4/0-8-8 mode sequence id in lookupTable stored in config blobk + +/* + * Serial NOR configuration block + */ +typedef struct _flexspi_nor_config +{ + flexspi_mem_config_t memConfig; //!< Common memory configuration info via FlexSPI + uint32_t pageSize; //!< Page size of Serial NOR + uint32_t sectorSize; //!< Sector size of Serial NOR + uint8_t ipcmdSerialClkFreq; //!< Clock frequency for IP command + uint8_t isUniformBlockSize; //!< Sector/Block size is the same + uint8_t reserved0[2]; //!< Reserved for future use + uint8_t serialNorType; //!< Serial NOR Flash type: 0/1/2/3 + uint8_t needExitNoCmdMode; //!< Need to exit NoCmd mode before other IP command + uint8_t halfClkForNonReadCmd; //!< Half the Serial Clock for non-read command: true/false + uint8_t needRestoreNoCmdMode; //!< Need to Restore NoCmd mode after IP commmand execution + uint32_t blockSize; //!< Block size + uint32_t reserve2[11]; //!< Reserved for future use +} flexspi_nor_config_t; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif +#endif /* __EVKMIMXRT1064_FLEXSPI_NOR_CONFIG__ */ diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/flash_config.c b/ports/mimxrt/boards/MIMXRT1064_EVK/flash_config.c new file mode 100644 index 0000000000..bfb1c2d59a --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1064_EVK/flash_config.c @@ -0,0 +1,49 @@ +/* + * Copyright 2018 NXP + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "evkmimxrt1064_flexspi_nor_config.h" + +/* Component ID definition, used by tools. */ +#ifndef FSL_COMPONENT_ID +#define FSL_COMPONENT_ID "platform.drivers.xip_board" +#endif + +/******************************************************************************* + * Code + ******************************************************************************/ +#if defined(XIP_BOOT_HEADER_ENABLE) && (XIP_BOOT_HEADER_ENABLE == 1) +#if defined(__CC_ARM) || defined(__ARMCC_VERSION) || defined(__GNUC__) +__attribute__((section(".boot_hdr.conf"))) +#elif defined(__ICCARM__) +#pragma location = ".boot_hdr.conf" +#endif + +const flexspi_nor_config_t qspiflash_config = { + .memConfig = + { + .tag = FLEXSPI_CFG_BLK_TAG, + .version = FLEXSPI_CFG_BLK_VERSION, + .readSampleClkSrc = kFlexSPIReadSampleClk_LoopbackFromDqsPad, + .csHoldTime = 3u, + .csSetupTime = 3u, + // Enable DDR mode, Wordaddassable, Safe configuration, Differential clock + .sflashPadType = kSerialFlash_4Pads, + .serialClkFreq = kFlexSpiSerialClk_100MHz, + .sflashA1Size = 8u * 1024u * 1024u, + .lookupTable = + { + // Read LUTs + FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0xEB, RADDR_SDR, FLEXSPI_4PAD, 0x18), + FLEXSPI_LUT_SEQ(DUMMY_SDR, FLEXSPI_4PAD, 0x06, READ_SDR, FLEXSPI_4PAD, 0x04), + }, + }, + .pageSize = 256u, + .sectorSize = 4u * 1024u, + .blockSize = 256u * 1024u, + .isUniformBlockSize = false, +}; +#endif /* XIP_BOOT_HEADER_ENABLE */ diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.h new file mode 100644 index 0000000000..e05c8824d2 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.h @@ -0,0 +1,8 @@ +#define MICROPY_HW_BOARD_NAME "i.MX RT1064 EVK" +#define MICROPY_HW_MCU_NAME "MIMXRT1064DVL6A" + + +// MIMXRT1064_EVK has 1 user LED +#define MICROPY_HW_LED1_PIN (GPIO_AD_B0_09) +#define MICROPY_HW_LED_ON(pin) (mp_hal_pin_low(pin)) +#define MICROPY_HW_LED_OFF(pin) (mp_hal_pin_high(pin)) diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.mk b/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.mk new file mode 100644 index 0000000000..7009889191 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1064_EVK/mpconfigboard.mk @@ -0,0 +1,9 @@ +MCU_SERIES = MIMXRT1064 +MCU_VARIANT = MIMXRT1064DVL6A + +JLINK_PATH ?= /media/RT1064-EVK/ + +CFLAGS += -DBOARD_FLASH_SIZE=0x400000 + +deploy: $(BUILD)/firmware.bin + cp $< $(JLINK_PATH) diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/pins.c b/ports/mimxrt/boards/MIMXRT1064_EVK/pins.c new file mode 100644 index 0000000000..d5da9c6f99 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1064_EVK/pins.c @@ -0,0 +1,33 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 NXP + * + * 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 "pin.h" + +static pin_af_obj_t GPIO_AD_B0_09_af[] = { + PIN_AF(GPIO1_IO09, PIN_AF_MODE_ALT5, GPIO1, 0x10B0U), +}; + +pin_obj_t GPIO_AD_B0_09 = PIN(GPIO_AD_B0_09, GPIO1, 9, GPIO_AD_B0_09_af); diff --git a/ports/mimxrt/boards/MIMXRT1064_EVK/pins.h b/ports/mimxrt/boards/MIMXRT1064_EVK/pins.h new file mode 100644 index 0000000000..baef51c6c8 --- /dev/null +++ b/ports/mimxrt/boards/MIMXRT1064_EVK/pins.h @@ -0,0 +1,30 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 NXP + * + * 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. + */ + +// NOTE: pins.h shall only be included in in pin.h +// hence no include guards are needed since they will be provided by pin.h + +extern pin_obj_t GPIO_AD_B0_09; From 4f2fe346239b6b3f720708021c868a88d97bb16d Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 4 Sep 2020 16:12:09 +1000 Subject: [PATCH 033/337] tools/mpy-tool.py: Fix merge of multiple mpy files to POP_TOP correctly. MP_BC_CALL_FUNCTION will leave the result on the Python stack, so that result must be discarded by MP_BC_POP_TOP. Signed-off-by: Damien George --- tools/mpy-tool.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/mpy-tool.py b/tools/mpy-tool.py index 1ac6c93d7d..de7cfe5d63 100755 --- a/tools/mpy-tool.py +++ b/tools/mpy-tool.py @@ -938,7 +938,7 @@ def merge_mpy(raw_codes, output_file): merged_mpy.extend(header) bytecode = bytearray() - bytecode_len = 6 + len(raw_codes) * 4 + 2 + bytecode_len = 6 + len(raw_codes) * 5 + 2 bytecode.append(bytecode_len << 2) # kind and length bytecode.append(0b00000000) # signature prelude bytecode.append(0b00001000) # size prelude @@ -947,7 +947,7 @@ def merge_mpy(raw_codes, output_file): for idx in range(len(raw_codes)): bytecode.append(0x32) # MP_BC_MAKE_FUNCTION bytecode.append(idx) # index raw code - bytecode.extend(b"\x34\x00") # MP_BC_CALL_FUNCTION, 0 args + bytecode.extend(b"\x34\x00\x59") # MP_BC_CALL_FUNCTION, 0 args, MP_BC_POP_TOP bytecode.extend(b"\x51\x63") # MP_BC_LOAD_NONE, MP_BC_RETURN_VALUE bytecode.append(0) # n_obj From 75344af4cada48b8b044ae35d0e62c38c601d899 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 8 Sep 2020 13:52:13 +1000 Subject: [PATCH 034/337] nrf/main: Make mp_builtin_open signature match that in py/builtin.h. Signed-off-by: Damien George --- ports/nrf/main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ports/nrf/main.c b/ports/nrf/main.c index 670c88e7ca..86ba83342b 100644 --- a/ports/nrf/main.c +++ b/ports/nrf/main.c @@ -280,10 +280,10 @@ mp_import_stat_t mp_import_stat(const char *path) { return uos_mbfs_import_stat(path); } -STATIC mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args) { +mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { return uos_mbfs_open(n_args, args); } -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_open_obj, 1, 2, mp_builtin_open); +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open); #else // use dummy functions - no filesystem available @@ -295,7 +295,7 @@ mp_import_stat_t mp_import_stat(const char *path) { return MP_IMPORT_STAT_NO_EXIST; } -STATIC mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { +mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { mp_raise_OSError(MP_EPERM); } MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open); From 27e117307d7d5b63955940866e6094bedb7d299e Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 8 Sep 2020 13:52:56 +1000 Subject: [PATCH 035/337] nrf: Remove unnecessary includes of mpconfigport.h and its header guard. The mpconfigport.h file is an internal header and should only ever be included once by mpconfig.h. Signed-off-by: Damien George --- ports/nrf/drivers/bluetooth/ble_drv.c | 1 - ports/nrf/modules/uos/microbitfs.c | 1 - ports/nrf/mpconfigport.h | 5 ----- 3 files changed, 7 deletions(-) diff --git a/ports/nrf/drivers/bluetooth/ble_drv.c b/ports/nrf/drivers/bluetooth/ble_drv.c index 1a64cdfbd2..3a15025cb4 100644 --- a/ports/nrf/drivers/bluetooth/ble_drv.c +++ b/ports/nrf/drivers/bluetooth/ble_drv.c @@ -32,7 +32,6 @@ #include "py/runtime.h" #include "ble_drv.h" -#include "mpconfigport.h" #include "nrf_sdm.h" #include "ble_gap.h" #include "ble.h" // sd_ble_uuid_encode diff --git a/ports/nrf/modules/uos/microbitfs.c b/ports/nrf/modules/uos/microbitfs.c index 7f16372cf2..e8628f4945 100644 --- a/ports/nrf/modules/uos/microbitfs.c +++ b/ports/nrf/modules/uos/microbitfs.c @@ -36,7 +36,6 @@ #include "py/stream.h" #include "py/runtime.h" #include "extmod/vfs.h" -#include "mpconfigport.h" #if MICROPY_MBFS diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index 1197df016c..76bb6d7bdf 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -24,9 +24,6 @@ * THE SOFTWARE. */ -#ifndef NRF5_MPCONFIGPORT_H__ -#define NRF5_MPCONFIGPORT_H__ - #include #if defined(NRF51822) @@ -342,5 +339,3 @@ extern const struct _mp_obj_module_t ble_module; #include #define MICROPY_PIN_DEFS_PORT_H "pin_defs_nrf5.h" - -#endif From 547688c58cce3436082cc9338929c2699410c0e1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 10 Sep 2020 20:43:07 +1000 Subject: [PATCH 036/337] stm32/usb: Don't nul pyb_hid_report_desc if MICROPY_HW_USB_HID disabled. So this code can be used if pyb_hid_report_desc is not included in the port's root pointer list. Signed-off-by: Damien George --- ports/stm32/usb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/stm32/usb.c b/ports/stm32/usb.c index 657d4cb6e7..968b2999ce 100644 --- a/ports/stm32/usb.c +++ b/ports/stm32/usb.c @@ -221,7 +221,9 @@ void pyb_usb_init0(void) { for (int i = 0; i < MICROPY_HW_USB_CDC_NUM; ++i) { usb_device.usbd_cdc_itf[i].attached_to_repl = false; } + #if MICROPY_HW_USB_HID MP_STATE_PORT(pyb_hid_report_desc) = MP_OBJ_NULL; + #endif pyb_usb_vcp_init0(); } From 709398daae14b3630de7050e08b9e77ea1232716 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 10 Sep 2020 20:46:40 +1000 Subject: [PATCH 037/337] stm32/rtc.h: Include py/obj.h to make header self contained. Signed-off-by: Damien George --- ports/stm32/rtc.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/stm32/rtc.h b/ports/stm32/rtc.h index d3840c1b73..5c454b3e39 100644 --- a/ports/stm32/rtc.h +++ b/ports/stm32/rtc.h @@ -26,6 +26,8 @@ #ifndef MICROPY_INCLUDED_STM32_RTC_H #define MICROPY_INCLUDED_STM32_RTC_H +#include "py/obj.h" + extern RTC_HandleTypeDef RTCHandle; extern const mp_obj_type_t pyb_rtc_type; From 8d5a40c86e384bf3cddb2f687374e0bb1ae6df7d Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 4 Sep 2020 11:04:46 +1000 Subject: [PATCH 038/337] py/objfloat: Fix handling of negative float to power of nan. Prior to this commit, pow(-2, float('nan')) would return (nan+nanj), or raise an exception on targets that don't support complex numbers. This is fixed to return simply nan, as CPython does. Signed-off-by: Damien George --- py/objfloat.c | 2 +- tests/float/inf_nan_arith.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 tests/float/inf_nan_arith.py diff --git a/py/objfloat.c b/py/objfloat.c index 09b73c8cda..f1e401ecce 100644 --- a/py/objfloat.c +++ b/py/objfloat.c @@ -293,7 +293,7 @@ mp_obj_t mp_obj_float_binary_op(mp_binary_op_t op, mp_float_t lhs_val, mp_obj_t if (lhs_val == 0 && rhs_val < 0 && !isinf(rhs_val)) { goto zero_division_error; } - if (lhs_val < 0 && rhs_val != MICROPY_FLOAT_C_FUN(floor)(rhs_val)) { + if (lhs_val < 0 && rhs_val != MICROPY_FLOAT_C_FUN(floor)(rhs_val) && !isnan(rhs_val)) { #if MICROPY_PY_BUILTINS_COMPLEX return mp_obj_complex_binary_op(MP_BINARY_OP_POWER, lhs_val, 0, rhs_in); #else diff --git a/tests/float/inf_nan_arith.py b/tests/float/inf_nan_arith.py new file mode 100644 index 0000000000..c27e38bc52 --- /dev/null +++ b/tests/float/inf_nan_arith.py @@ -0,0 +1,20 @@ +# Test behaviour of inf and nan in basic float operations + +inf = float("inf") +nan = float("nan") + +values = (-2, -1, 0, 1, 2, inf, nan) + +for x in values: + for y in values: + print(x, y) + print(" + - *", x + y, x - y, x * y) + try: + print(" /", x / y) + except ZeroDivisionError: + print(" / ZeroDivisionError") + try: + print(" ** pow", x ** y, pow(x, y)) + except ZeroDivisionError: + print(" ** pow ZeroDivisionError") + print(" == != < <= > >=", x == y, x != y, x < y, x <= y, x > y, x >= y) From 2e54d9d146b34d7ad00e4394c9767f4319244cdf Mon Sep 17 00:00:00 2001 From: stijn Date: Tue, 8 Sep 2020 15:22:34 +0200 Subject: [PATCH 039/337] py: Fix handling of NaN in certain pow implementations. Adds a new compile-time option MICROPY_PY_MATH_POW_FIX_NAN for use with toolchains that don't handle pow-of-NaN correctly. --- ports/windows/mpconfigport.h | 2 ++ py/modmath.c | 12 ++++++++++++ py/mpconfig.h | 5 +++++ py/objfloat.c | 6 ++++++ tests/float/math_domain.py | 2 +- 5 files changed, 26 insertions(+), 1 deletion(-) diff --git a/ports/windows/mpconfigport.h b/ports/windows/mpconfigport.h index 30389c700e..faf10752ee 100644 --- a/ports/windows/mpconfigport.h +++ b/ports/windows/mpconfigport.h @@ -236,6 +236,8 @@ extern const struct _mp_obj_module_t mp_module_time; #define MICROPY_PY_MATH_FMOD_FIX_INFNAN (1) #ifdef _WIN64 #define MICROPY_PY_MATH_MODF_FIX_NEGZERO (1) +#else +#define MICROPY_PY_MATH_POW_FIX_NAN (1) #endif #endif diff --git a/py/modmath.c b/py/modmath.c index b312eeb3d2..b7948f39e7 100644 --- a/py/modmath.c +++ b/py/modmath.c @@ -98,7 +98,19 @@ mp_float_t MICROPY_FLOAT_C_FUN(log2)(mp_float_t x) { // sqrt(x): returns the square root of x MATH_FUN_1(sqrt, sqrt) // pow(x, y): returns x to the power of y +#if MICROPY_PY_MATH_POW_FIX_NAN +mp_float_t pow_func(mp_float_t x, mp_float_t y) { + // pow(base, 0) returns 1 for any base, even when base is NaN + // pow(+1, exponent) returns 1 for any exponent, even when exponent is NaN + if (x == MICROPY_FLOAT_CONST(1.0) || y == MICROPY_FLOAT_CONST(0.0)) { + return MICROPY_FLOAT_CONST(1.0); + } + return MICROPY_FLOAT_C_FUN(pow)(x, y); +} +MATH_FUN_2(pow, pow_func) +#else MATH_FUN_2(pow, pow) +#endif // exp(x) MATH_FUN_1(exp, exp) #if MICROPY_PY_MATH_SPECIAL_FUNCTIONS diff --git a/py/mpconfig.h b/py/mpconfig.h index ae4cabfdcf..12517316c6 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1160,6 +1160,11 @@ typedef double mp_float_t; #define MICROPY_PY_MATH_MODF_FIX_NEGZERO (0) #endif +// Whether to provide fix for pow(1, NaN) and pow(NaN, 0), which both should be 1 not NaN. +#ifndef MICROPY_PY_MATH_POW_FIX_NAN +#define MICROPY_PY_MATH_POW_FIX_NAN (0) +#endif + // Whether to provide "cmath" module #ifndef MICROPY_PY_CMATH #define MICROPY_PY_CMATH (0) diff --git a/py/objfloat.c b/py/objfloat.c index f1e401ecce..451609492e 100644 --- a/py/objfloat.c +++ b/py/objfloat.c @@ -300,6 +300,12 @@ mp_obj_t mp_obj_float_binary_op(mp_binary_op_t op, mp_float_t lhs_val, mp_obj_t mp_raise_ValueError(MP_ERROR_TEXT("complex values not supported")); #endif } + #if MICROPY_PY_MATH_POW_FIX_NAN // Also see modmath.c. + if (lhs_val == MICROPY_FLOAT_CONST(1.0) || rhs_val == MICROPY_FLOAT_CONST(0.0)) { + lhs_val = MICROPY_FLOAT_CONST(1.0); + break; + } + #endif lhs_val = MICROPY_FLOAT_C_FUN(pow)(lhs_val, rhs_val); break; case MP_BINARY_OP_DIVMOD: { diff --git a/tests/float/math_domain.py b/tests/float/math_domain.py index e63628cf50..0c25dc08b6 100644 --- a/tests/float/math_domain.py +++ b/tests/float/math_domain.py @@ -38,7 +38,7 @@ for name, f, args in ( # double argument functions for name, f, args in ( - ("pow", math.pow, ((0, 2), (-1, 2), (0, -1), (-1, 2.3))), + ("pow", math.pow, ((0, 2), (-1, 2), (0, -1), (-1, 2.3), (nan, 0), (1, nan))), ("fmod", math.fmod, ((1.2, inf), (1.2, -inf), (1.2, 0), (inf, 1.2))), ("atan2", math.atan2, ((0, 0), (-inf, inf), (-inf, -inf), (inf, -inf))), ("copysign", math.copysign, ()), From 373b400632bb5371f1e259983850814d2384e667 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 4 Sep 2020 00:53:18 +1000 Subject: [PATCH 040/337] extmod/modussl_axtls: Reduce size of code that makes exception. Change in code size (for ports that use axtls) is: unix x64: -152 -0.030% [incl -160(data)] unix nanbox: -112 -0.025% [incl -96(data)] esp8266: -64 -0.009% GENERIC Signed-off-by: Damien George --- extmod/modussl_axtls.c | 96 ++++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 42 deletions(-) diff --git a/extmod/modussl_axtls.c b/extmod/modussl_axtls.c index 0b0ce35fc8..da5941a55b 100644 --- a/extmod/modussl_axtls.c +++ b/extmod/modussl_axtls.c @@ -55,53 +55,65 @@ struct ssl_args { STATIC const mp_obj_type_t ussl_socket_type; -// Table of errors -struct ssl_errs { - int16_t errnum; - const char *errstr; +// Table of error strings corresponding to SSL_xxx error codes. +STATIC const char *const ssl_error_tab1[] = { + "NOT_OK", + "DEAD", + "CLOSE_NOTIFY", + "EAGAIN", }; -STATIC const struct ssl_errs ssl_error_tab[] = { - { SSL_NOT_OK, "NOT_OK" }, - { SSL_ERROR_DEAD, "DEAD" }, - { SSL_CLOSE_NOTIFY, "CLOSE_NOTIFY" }, - { SSL_EAGAIN, "EAGAIN" }, - { SSL_ERROR_CONN_LOST, "CONN_LOST" }, - { SSL_ERROR_RECORD_OVERFLOW, "RECORD_OVERFLOW" }, - { SSL_ERROR_SOCK_SETUP_FAILURE, "SOCK_SETUP_FAILURE" }, - { SSL_ERROR_INVALID_HANDSHAKE, "INVALID_HANDSHAKE" }, - { SSL_ERROR_INVALID_PROT_MSG, "INVALID_PROT_MSG" }, - { SSL_ERROR_INVALID_HMAC, "INVALID_HMAC" }, - { SSL_ERROR_INVALID_VERSION, "INVALID_VERSION" }, - { SSL_ERROR_UNSUPPORTED_EXTENSION, "UNSUPPORTED_EXTENSION" }, - { SSL_ERROR_INVALID_SESSION, "INVALID_SESSION" }, - { SSL_ERROR_NO_CIPHER, "NO_CIPHER" }, - { SSL_ERROR_INVALID_CERT_HASH_ALG, "INVALID_CERT_HASH_ALG" }, - { SSL_ERROR_BAD_CERTIFICATE, "BAD_CERTIFICATE" }, - { SSL_ERROR_INVALID_KEY, "INVALID_KEY" }, - { SSL_ERROR_FINISHED_INVALID, "FINISHED_INVALID" }, - { SSL_ERROR_NO_CERT_DEFINED, "NO_CERT_DEFINED" }, - { SSL_ERROR_NO_CLIENT_RENOG, "NO_CLIENT_RENOG" }, - { SSL_ERROR_NOT_SUPPORTED, "NOT_SUPPORTED" }, +STATIC const char *const ssl_error_tab2[] = { + "CONN_LOST", + "RECORD_OVERFLOW", + "SOCK_SETUP_FAILURE", + NULL, + "INVALID_HANDSHAKE", + "INVALID_PROT_MSG", + "INVALID_HMAC", + "INVALID_VERSION", + "UNSUPPORTED_EXTENSION", + "INVALID_SESSION", + "NO_CIPHER", + "INVALID_CERT_HASH_ALG", + "BAD_CERTIFICATE", + "INVALID_KEY", + NULL, + "FINISHED_INVALID", + "NO_CERT_DEFINED", + "NO_CLIENT_RENOG", + "NOT_SUPPORTED", }; STATIC NORETURN void ussl_raise_error(int err) { - for (size_t i = 0; i < MP_ARRAY_SIZE(ssl_error_tab); i++) { - if (ssl_error_tab[i].errnum == err) { - // construct string object - mp_obj_str_t *o_str = m_new_obj_maybe(mp_obj_str_t); - if (o_str == NULL) { - break; - } - o_str->base.type = &mp_type_str; - o_str->data = (const byte *)ssl_error_tab[i].errstr; - o_str->len = strlen((char *)o_str->data); - o_str->hash = qstr_compute_hash(o_str->data, o_str->len); - // raise - mp_obj_t args[2] = { MP_OBJ_NEW_SMALL_INT(err), MP_OBJ_FROM_PTR(o_str)}; - nlr_raise(mp_obj_exception_make_new(&mp_type_OSError, 2, 0, args)); - } + MP_STATIC_ASSERT(SSL_NOT_OK - 3 == SSL_EAGAIN); + MP_STATIC_ASSERT(SSL_ERROR_CONN_LOST - 18 == SSL_ERROR_NOT_SUPPORTED); + + // Check if err corresponds to something in one of the error string tables. + const char *errstr = NULL; + if (SSL_NOT_OK >= err && err >= SSL_EAGAIN) { + errstr = ssl_error_tab1[SSL_NOT_OK - err]; + } else if (SSL_ERROR_CONN_LOST >= err && err >= SSL_ERROR_NOT_SUPPORTED) { + errstr = ssl_error_tab2[SSL_ERROR_CONN_LOST - err]; } - mp_raise_OSError(err); + + // Unknown error, just raise the error code. + if (errstr == NULL) { + mp_raise_OSError(err); + } + + // Construct string object. + mp_obj_str_t *o_str = m_new_obj_maybe(mp_obj_str_t); + if (o_str == NULL) { + mp_raise_OSError(err); + } + o_str->base.type = &mp_type_str; + o_str->data = (const byte *)errstr; + o_str->len = strlen((char *)o_str->data); + o_str->hash = qstr_compute_hash(o_str->data, o_str->len); + + // Raise OSError(err, str). + mp_obj_t args[2] = { MP_OBJ_NEW_SMALL_INT(err), MP_OBJ_FROM_PTR(o_str)}; + nlr_raise(mp_obj_exception_make_new(&mp_type_OSError, 2, 0, args)); } From 4b35aa5730cf26992453e3ce7cf74ced4128fe20 Mon Sep 17 00:00:00 2001 From: stijn Date: Thu, 10 Sep 2020 12:22:49 +0200 Subject: [PATCH 041/337] tools: Write msvc-compatible frozen content. The msvc compiler doesn't accept zero-sized arrays so let the freezing process generate compatible C code in case no modules are found and the involved arrays are all empty. This doesn't affect the functionality in any way because those arrays only get accessed when mp_frozen_mpy_names contains names, i.e. when modules are actually found. --- tools/make-frozen.py | 4 ++-- tools/makemanifest.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/make-frozen.py b/tools/make-frozen.py index a8706bf784..bc35d38342 100755 --- a/tools/make-frozen.py +++ b/tools/make-frozen.py @@ -50,7 +50,7 @@ print("const uint32_t mp_frozen_str_sizes[] = {") for f, st in modules: print("%d," % st.st_size) -print("};") +print("0};") print("const char mp_frozen_str_content[] = {") for f, st in modules: @@ -82,4 +82,4 @@ for f, st in modules: chrs.append('\\0"') print("".join(chrs)) -print("};") +print('"\\0"};') diff --git a/tools/makemanifest.py b/tools/makemanifest.py index 377f245596..9ef0368260 100644 --- a/tools/makemanifest.py +++ b/tools/makemanifest.py @@ -327,7 +327,7 @@ def main(): b" (qstr_pool_t*)&mp_qstr_const_pool, MP_QSTRnumber_of, 0, 0\n" b"};\n" b'const char mp_frozen_mpy_names[1] = {"\\0"};\n' - b"const mp_raw_code_t *const mp_frozen_mpy_content[0] = {};\n" + b"const mp_raw_code_t *const mp_frozen_mpy_content[1] = {NULL};\n" ) # Generate output From 2a9ea69fa93b1b940b802f981f8835f6decdb085 Mon Sep 17 00:00:00 2001 From: stijn Date: Thu, 10 Sep 2020 12:11:58 +0200 Subject: [PATCH 042/337] windows/msvc: Support freezing modules. Support freezing modules via manifest.py for consistency with the other ports. In essence this comes down to calling makemanifest.py and adding the resulting .c file to the build. Note the file with preprocessed qstrs has been renamed to match what makemanifest.py expects and which is also the name all other ports use. --- ports/windows/micropython.vcxproj | 2 +- ports/windows/msvc/genhdr.targets | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/ports/windows/micropython.vcxproj b/ports/windows/micropython.vcxproj index f70fe96158..73a837a840 100644 --- a/ports/windows/micropython.vcxproj +++ b/ports/windows/micropython.vcxproj @@ -104,7 +104,7 @@ - + diff --git a/ports/windows/msvc/genhdr.targets b/ports/windows/msvc/genhdr.targets index 327f922e4a..3af0ea2636 100644 --- a/ports/windows/msvc/genhdr.targets +++ b/ports/windows/msvc/genhdr.targets @@ -55,7 +55,7 @@ using(var outFile = System.IO.File.CreateText(OutputFile)) { - + $([System.String]::new('%(FullPath)').Replace('$(PyBaseDir)', '$(DestDir)qstr\')) @@ -101,8 +101,8 @@ using(var outFile = System.IO.File.CreateText(OutputFile)) { $(QstrGen).tmp - - + + @@ -115,6 +115,17 @@ using(var outFile = System.IO.File.CreateText(OutputFile)) { + + + + + MICROPY_MODULE_FROZEN_MPY=1;MICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool;%(PreprocessorDefinitions) + + + + + + From 5b94c610971334019e7301026e0fbbf5950e094d Mon Sep 17 00:00:00 2001 From: stijn Date: Thu, 10 Sep 2020 14:13:25 +0200 Subject: [PATCH 043/337] windows/Makefile: Support freezing modules. Alter the build flags as needed to support freezing modules with a manifest. This makes freezing works just like it does for e.g. the unix port. --- ports/windows/Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/windows/Makefile b/ports/windows/Makefile index b2d11872ed..3bfbc18304 100644 --- a/ports/windows/Makefile +++ b/ports/windows/Makefile @@ -58,4 +58,9 @@ SRC_QSTR += $(SRC_C) # SRC_QSTR SRC_QSTR_AUTO_DEPS += +ifneq ($(FROZEN_MANIFEST),) +CFLAGS += -DMICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool -DMICROPY_MODULE_FROZEN_MPY=1 -DMPZ_DIG_SIZE=16 +MPY_CROSS_FLAGS += -mcache-lookup-bc +endif + include $(TOP)/py/mkrules.mk From 70bec41089eef26122d6c2cbd06bf359bc0ab253 Mon Sep 17 00:00:00 2001 From: stijn Date: Thu, 10 Sep 2020 13:21:21 +0200 Subject: [PATCH 044/337] windows: Show test failures in the Appveyor builds. --- ports/windows/.appveyor.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ports/windows/.appveyor.yml b/ports/windows/.appveyor.yml index 2965543b05..4d2d6bd11d 100644 --- a/ports/windows/.appveyor.yml +++ b/ports/windows/.appveyor.yml @@ -38,7 +38,15 @@ test_script: - ps: | cd (Join-Path $env:APPVEYOR_BUILD_FOLDER 'tests') & $env:MICROPY_CPYTHON3 run-tests + if ($LASTEXITCODE -ne 0) { + & $env:MICROPY_CPYTHON3 run-tests --print-failures + throw "Test failure" + } & $env:MICROPY_CPYTHON3 run-tests --via-mpy -d basics float micropython + if ($LASTEXITCODE -ne 0) { + & $env:MICROPY_CPYTHON3 run-tests --print-failures + throw "Test failure" + } # After the build/test phase for the MSVC build completes, # build and test with mingw-w64, release versions only. @@ -69,9 +77,11 @@ after_test: } & $env:MICROPY_CPYTHON3 $testArgs if ($LASTEXITCODE -ne 0) { - throw "$env:MSYSTEM tests exited with code $LASTEXITCODE" + & $env:MICROPY_CPYTHON3 run-tests --print-failures + throw "Test failure" } & $env:MICROPY_CPYTHON3 ($testArgs + @('--via-mpy', '-d', 'basics', 'float', 'micropython')) if ($LASTEXITCODE -ne 0) { - throw "$env:MSYSTEM mpy-cross tests exited with code $LASTEXITCODE" + & $env:MICROPY_CPYTHON3 run-tests --print-failures + throw "Test failure" } From 50efce81740901fee6f5191db36a48ada13ef6df Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Thu, 10 Sep 2020 11:31:01 +1000 Subject: [PATCH 045/337] esp32/mpconfigport.h: Remove duplicate uhashlib registration. --- ports/esp32/mpconfigport.h | 1 - 1 file changed, 1 deletion(-) diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 7c09bdbd92..663fed3f68 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -200,7 +200,6 @@ extern const struct _mp_obj_module_t mp_module_onewire; { MP_OBJ_NEW_QSTR(MP_QSTR_machine), (mp_obj_t)&mp_module_machine }, \ { MP_OBJ_NEW_QSTR(MP_QSTR_network), (mp_obj_t)&mp_module_network }, \ { MP_OBJ_NEW_QSTR(MP_QSTR__onewire), (mp_obj_t)&mp_module_onewire }, \ - { MP_OBJ_NEW_QSTR(MP_QSTR_uhashlib), (mp_obj_t)&mp_module_uhashlib }, \ #define MP_STATE_PORT MP_STATE_VM From 85f2b239d8ff8d29155c63799717e3f88b2f33f3 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 11 Sep 2020 17:22:28 +1000 Subject: [PATCH 046/337] py/showbc: Pass in an mp_print_t struct to all bytecode-print functions. So the output can be redirected if needed. Signed-off-by: Damien George --- py/bc.h | 8 +- py/emitglue.c | 2 +- py/showbc.c | 209 +++++++++++++++++++++++++------------------------- py/vm.c | 2 +- 4 files changed, 109 insertions(+), 112 deletions(-) diff --git a/py/bc.h b/py/bc.h index 0c19c51659..16f314e199 100644 --- a/py/bc.h +++ b/py/bc.h @@ -226,10 +226,10 @@ const byte *mp_decode_uint_skip(const byte *ptr); mp_vm_return_kind_t mp_execute_bytecode(mp_code_state_t *code_state, volatile mp_obj_t inject_exc); mp_code_state_t *mp_obj_fun_bc_prepare_codestate(mp_obj_t func, size_t n_args, size_t n_kw, const mp_obj_t *args); void mp_setup_code_state(mp_code_state_t *code_state, size_t n_args, size_t n_kw, const mp_obj_t *args); -void mp_bytecode_print(const void *descr, const byte *code, mp_uint_t len, const mp_uint_t *const_table); -void mp_bytecode_print2(const byte *code, size_t len, const mp_uint_t *const_table); -const byte *mp_bytecode_print_str(const byte *ip); -#define mp_bytecode_print_inst(code, const_table) mp_bytecode_print2(code, 1, const_table) +void mp_bytecode_print(const mp_print_t *print, const void *descr, const byte *code, mp_uint_t len, const mp_uint_t *const_table); +void mp_bytecode_print2(const mp_print_t *print, const byte *code, size_t len, const mp_uint_t *const_table); +const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip); +#define mp_bytecode_print_inst(print, code, const_table) mp_bytecode_print2(print, code, 1, const_table) // Helper macros to access pointer with least significant bits holding flags #define MP_TAGPTR_PTR(x) ((void *)((uintptr_t)(x) & ~((uintptr_t)3))) diff --git a/py/emitglue.c b/py/emitglue.c index 98320c426b..0ef708a3f3 100644 --- a/py/emitglue.c +++ b/py/emitglue.c @@ -92,7 +92,7 @@ void mp_emit_glue_assign_bytecode(mp_raw_code_t *rc, const byte *code, #endif #if MICROPY_DEBUG_PRINTERS if (mp_verbose_flag >= 2) { - mp_bytecode_print(rc, code, len, const_table); + mp_bytecode_print(&mp_plat_print, rc, code, len, const_table); } #endif } diff --git a/py/showbc.c b/py/showbc.c index 4d6527fae2..8941dd7544 100644 --- a/py/showbc.c +++ b/py/showbc.c @@ -32,9 +32,6 @@ #if MICROPY_DEBUG_PRINTERS -// redirect all printfs in this file to the platform print stream -#define printf(...) mp_printf(&mp_plat_print, __VA_ARGS__) - #define DECODE_UINT { \ unum = 0; \ do { \ @@ -80,7 +77,7 @@ const byte *mp_showbc_code_start; const mp_uint_t *mp_showbc_const_table; -void mp_bytecode_print(const void *descr, const byte *ip, mp_uint_t len, const mp_uint_t *const_table) { +void mp_bytecode_print(const mp_print_t *print, const void *descr, const byte *ip, mp_uint_t len, const mp_uint_t *const_table) { mp_showbc_code_start = ip; // Decode prelude @@ -96,30 +93,30 @@ void mp_bytecode_print(const void *descr, const byte *ip, mp_uint_t len, const m qstr block_name = mp_decode_uint(&code_info); qstr source_file = mp_decode_uint(&code_info); #endif - printf("File %s, code block '%s' (descriptor: %p, bytecode @%p " UINT_FMT " bytes)\n", + mp_printf(print, "File %s, code block '%s' (descriptor: %p, bytecode @%p " UINT_FMT " bytes)\n", qstr_str(source_file), qstr_str(block_name), descr, mp_showbc_code_start, len); // raw bytecode dump size_t prelude_size = ip - mp_showbc_code_start + n_info + n_cell; - printf("Raw bytecode (code_info_size=" UINT_FMT ", bytecode_size=" UINT_FMT "):\n", + mp_printf(print, "Raw bytecode (code_info_size=" UINT_FMT ", bytecode_size=" UINT_FMT "):\n", prelude_size, len - prelude_size); for (mp_uint_t i = 0; i < len; i++) { if (i > 0 && i % 16 == 0) { - printf("\n"); + mp_printf(print, "\n"); } - printf(" %02x", mp_showbc_code_start[i]); + mp_printf(print, " %02x", mp_showbc_code_start[i]); } - printf("\n"); + mp_printf(print, "\n"); // bytecode prelude: arg names (as qstr objects) - printf("arg names:"); + mp_printf(print, "arg names:"); for (mp_uint_t i = 0; i < n_pos_args + n_kwonly_args; i++) { - printf(" %s", qstr_str(MP_OBJ_QSTR_VALUE(const_table[i]))); + mp_printf(print, " %s", qstr_str(MP_OBJ_QSTR_VALUE(const_table[i]))); } - printf("\n"); + mp_printf(print, "\n"); - printf("(N_STATE %u)\n", (unsigned)n_state); - printf("(N_EXC_STACK %u)\n", (unsigned)n_exc_stack); + mp_printf(print, "(N_STATE %u)\n", (unsigned)n_state); + mp_printf(print, "(N_EXC_STACK %u)\n", (unsigned)n_exc_stack); // skip over code_info ip += n_info; @@ -127,14 +124,14 @@ void mp_bytecode_print(const void *descr, const byte *ip, mp_uint_t len, const m // bytecode prelude: initialise closed over variables for (size_t i = 0; i < n_cell; ++i) { uint local_num = *ip++; - printf("(INIT_CELL %u)\n", local_num); + mp_printf(print, "(INIT_CELL %u)\n", local_num); } // print out line number info { mp_int_t bc = 0; mp_uint_t source_line = 1; - printf(" bc=" INT_FMT " line=" UINT_FMT "\n", bc, source_line); + mp_printf(print, " bc=" INT_FMT " line=" UINT_FMT "\n", bc, source_line); for (const byte *ci = code_info; *ci;) { if ((ci[0] & 0x80) == 0) { // 0b0LLBBBBB encoding @@ -147,27 +144,27 @@ void mp_bytecode_print(const void *descr, const byte *ip, mp_uint_t len, const m source_line += ((ci[0] << 4) & 0x700) | ci[1]; ci += 2; } - printf(" bc=" INT_FMT " line=" UINT_FMT "\n", bc, source_line); + mp_printf(print, " bc=" INT_FMT " line=" UINT_FMT "\n", bc, source_line); } } - mp_bytecode_print2(ip, len - prelude_size, const_table); + mp_bytecode_print2(print, ip, len - prelude_size, const_table); } -const byte *mp_bytecode_print_str(const byte *ip) { +const byte *mp_bytecode_print_str(const mp_print_t *print, const byte *ip) { mp_uint_t unum; qstr qst; switch (*ip++) { case MP_BC_LOAD_CONST_FALSE: - printf("LOAD_CONST_FALSE"); + mp_printf(print, "LOAD_CONST_FALSE"); break; case MP_BC_LOAD_CONST_NONE: - printf("LOAD_CONST_NONE"); + mp_printf(print, "LOAD_CONST_NONE"); break; case MP_BC_LOAD_CONST_TRUE: - printf("LOAD_CONST_TRUE"); + mp_printf(print, "LOAD_CONST_TRUE"); break; case MP_BC_LOAD_CONST_SMALL_INT: { @@ -179,197 +176,197 @@ const byte *mp_bytecode_print_str(const byte *ip) { do { num = (num << 7) | (*ip & 0x7f); } while ((*ip++ & 0x80) != 0); - printf("LOAD_CONST_SMALL_INT " INT_FMT, num); + mp_printf(print, "LOAD_CONST_SMALL_INT " INT_FMT, num); break; } case MP_BC_LOAD_CONST_STRING: DECODE_QSTR; - printf("LOAD_CONST_STRING '%s'", qstr_str(qst)); + mp_printf(print, "LOAD_CONST_STRING '%s'", qstr_str(qst)); break; case MP_BC_LOAD_CONST_OBJ: DECODE_OBJ; - printf("LOAD_CONST_OBJ %p=", MP_OBJ_TO_PTR(unum)); - mp_obj_print_helper(&mp_plat_print, (mp_obj_t)unum, PRINT_REPR); + mp_printf(print, "LOAD_CONST_OBJ %p=", MP_OBJ_TO_PTR(unum)); + mp_obj_print_helper(print, (mp_obj_t)unum, PRINT_REPR); break; case MP_BC_LOAD_NULL: - printf("LOAD_NULL"); + mp_printf(print, "LOAD_NULL"); break; case MP_BC_LOAD_FAST_N: DECODE_UINT; - printf("LOAD_FAST_N " UINT_FMT, unum); + mp_printf(print, "LOAD_FAST_N " UINT_FMT, unum); break; case MP_BC_LOAD_DEREF: DECODE_UINT; - printf("LOAD_DEREF " UINT_FMT, unum); + mp_printf(print, "LOAD_DEREF " UINT_FMT, unum); break; case MP_BC_LOAD_NAME: DECODE_QSTR; - printf("LOAD_NAME %s", qstr_str(qst)); + mp_printf(print, "LOAD_NAME %s", qstr_str(qst)); if (MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE) { - printf(" (cache=%u)", *ip++); + mp_printf(print, " (cache=%u)", *ip++); } break; case MP_BC_LOAD_GLOBAL: DECODE_QSTR; - printf("LOAD_GLOBAL %s", qstr_str(qst)); + mp_printf(print, "LOAD_GLOBAL %s", qstr_str(qst)); if (MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE) { - printf(" (cache=%u)", *ip++); + mp_printf(print, " (cache=%u)", *ip++); } break; case MP_BC_LOAD_ATTR: DECODE_QSTR; - printf("LOAD_ATTR %s", qstr_str(qst)); + mp_printf(print, "LOAD_ATTR %s", qstr_str(qst)); if (MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE) { - printf(" (cache=%u)", *ip++); + mp_printf(print, " (cache=%u)", *ip++); } break; case MP_BC_LOAD_METHOD: DECODE_QSTR; - printf("LOAD_METHOD %s", qstr_str(qst)); + mp_printf(print, "LOAD_METHOD %s", qstr_str(qst)); break; case MP_BC_LOAD_SUPER_METHOD: DECODE_QSTR; - printf("LOAD_SUPER_METHOD %s", qstr_str(qst)); + mp_printf(print, "LOAD_SUPER_METHOD %s", qstr_str(qst)); break; case MP_BC_LOAD_BUILD_CLASS: - printf("LOAD_BUILD_CLASS"); + mp_printf(print, "LOAD_BUILD_CLASS"); break; case MP_BC_LOAD_SUBSCR: - printf("LOAD_SUBSCR"); + mp_printf(print, "LOAD_SUBSCR"); break; case MP_BC_STORE_FAST_N: DECODE_UINT; - printf("STORE_FAST_N " UINT_FMT, unum); + mp_printf(print, "STORE_FAST_N " UINT_FMT, unum); break; case MP_BC_STORE_DEREF: DECODE_UINT; - printf("STORE_DEREF " UINT_FMT, unum); + mp_printf(print, "STORE_DEREF " UINT_FMT, unum); break; case MP_BC_STORE_NAME: DECODE_QSTR; - printf("STORE_NAME %s", qstr_str(qst)); + mp_printf(print, "STORE_NAME %s", qstr_str(qst)); break; case MP_BC_STORE_GLOBAL: DECODE_QSTR; - printf("STORE_GLOBAL %s", qstr_str(qst)); + mp_printf(print, "STORE_GLOBAL %s", qstr_str(qst)); break; case MP_BC_STORE_ATTR: DECODE_QSTR; - printf("STORE_ATTR %s", qstr_str(qst)); + mp_printf(print, "STORE_ATTR %s", qstr_str(qst)); if (MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE) { - printf(" (cache=%u)", *ip++); + mp_printf(print, " (cache=%u)", *ip++); } break; case MP_BC_STORE_SUBSCR: - printf("STORE_SUBSCR"); + mp_printf(print, "STORE_SUBSCR"); break; case MP_BC_DELETE_FAST: DECODE_UINT; - printf("DELETE_FAST " UINT_FMT, unum); + mp_printf(print, "DELETE_FAST " UINT_FMT, unum); break; case MP_BC_DELETE_DEREF: DECODE_UINT; - printf("DELETE_DEREF " UINT_FMT, unum); + mp_printf(print, "DELETE_DEREF " UINT_FMT, unum); break; case MP_BC_DELETE_NAME: DECODE_QSTR; - printf("DELETE_NAME %s", qstr_str(qst)); + mp_printf(print, "DELETE_NAME %s", qstr_str(qst)); break; case MP_BC_DELETE_GLOBAL: DECODE_QSTR; - printf("DELETE_GLOBAL %s", qstr_str(qst)); + mp_printf(print, "DELETE_GLOBAL %s", qstr_str(qst)); break; case MP_BC_DUP_TOP: - printf("DUP_TOP"); + mp_printf(print, "DUP_TOP"); break; case MP_BC_DUP_TOP_TWO: - printf("DUP_TOP_TWO"); + mp_printf(print, "DUP_TOP_TWO"); break; case MP_BC_POP_TOP: - printf("POP_TOP"); + mp_printf(print, "POP_TOP"); break; case MP_BC_ROT_TWO: - printf("ROT_TWO"); + mp_printf(print, "ROT_TWO"); break; case MP_BC_ROT_THREE: - printf("ROT_THREE"); + mp_printf(print, "ROT_THREE"); break; case MP_BC_JUMP: DECODE_SLABEL; - printf("JUMP " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "JUMP " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); break; case MP_BC_POP_JUMP_IF_TRUE: DECODE_SLABEL; - printf("POP_JUMP_IF_TRUE " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "POP_JUMP_IF_TRUE " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); break; case MP_BC_POP_JUMP_IF_FALSE: DECODE_SLABEL; - printf("POP_JUMP_IF_FALSE " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "POP_JUMP_IF_FALSE " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); break; case MP_BC_JUMP_IF_TRUE_OR_POP: DECODE_SLABEL; - printf("JUMP_IF_TRUE_OR_POP " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "JUMP_IF_TRUE_OR_POP " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); break; case MP_BC_JUMP_IF_FALSE_OR_POP: DECODE_SLABEL; - printf("JUMP_IF_FALSE_OR_POP " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "JUMP_IF_FALSE_OR_POP " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); break; case MP_BC_SETUP_WITH: DECODE_ULABEL; // loop-like labels are always forward - printf("SETUP_WITH " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "SETUP_WITH " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); break; case MP_BC_WITH_CLEANUP: - printf("WITH_CLEANUP"); + mp_printf(print, "WITH_CLEANUP"); break; case MP_BC_UNWIND_JUMP: DECODE_SLABEL; - printf("UNWIND_JUMP " UINT_FMT " %d", (mp_uint_t)(ip + unum - mp_showbc_code_start), *ip); + mp_printf(print, "UNWIND_JUMP " UINT_FMT " %d", (mp_uint_t)(ip + unum - mp_showbc_code_start), *ip); ip += 1; break; case MP_BC_SETUP_EXCEPT: DECODE_ULABEL; // except labels are always forward - printf("SETUP_EXCEPT " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "SETUP_EXCEPT " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); break; case MP_BC_SETUP_FINALLY: DECODE_ULABEL; // except labels are always forward - printf("SETUP_FINALLY " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "SETUP_FINALLY " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); break; case MP_BC_END_FINALLY: @@ -377,169 +374,169 @@ const byte *mp_bytecode_print_str(const byte *ip) { // if TOS is an integer, does something else // if TOS is None, just pops it and continues // else error - printf("END_FINALLY"); + mp_printf(print, "END_FINALLY"); break; case MP_BC_GET_ITER: - printf("GET_ITER"); + mp_printf(print, "GET_ITER"); break; case MP_BC_GET_ITER_STACK: - printf("GET_ITER_STACK"); + mp_printf(print, "GET_ITER_STACK"); break; case MP_BC_FOR_ITER: DECODE_ULABEL; // the jump offset if iteration finishes; for labels are always forward - printf("FOR_ITER " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "FOR_ITER " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); break; case MP_BC_POP_EXCEPT_JUMP: DECODE_ULABEL; // these labels are always forward - printf("POP_EXCEPT_JUMP " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); + mp_printf(print, "POP_EXCEPT_JUMP " UINT_FMT, (mp_uint_t)(ip + unum - mp_showbc_code_start)); break; case MP_BC_BUILD_TUPLE: DECODE_UINT; - printf("BUILD_TUPLE " UINT_FMT, unum); + mp_printf(print, "BUILD_TUPLE " UINT_FMT, unum); break; case MP_BC_BUILD_LIST: DECODE_UINT; - printf("BUILD_LIST " UINT_FMT, unum); + mp_printf(print, "BUILD_LIST " UINT_FMT, unum); break; case MP_BC_BUILD_MAP: DECODE_UINT; - printf("BUILD_MAP " UINT_FMT, unum); + mp_printf(print, "BUILD_MAP " UINT_FMT, unum); break; case MP_BC_STORE_MAP: - printf("STORE_MAP"); + mp_printf(print, "STORE_MAP"); break; case MP_BC_BUILD_SET: DECODE_UINT; - printf("BUILD_SET " UINT_FMT, unum); + mp_printf(print, "BUILD_SET " UINT_FMT, unum); break; #if MICROPY_PY_BUILTINS_SLICE case MP_BC_BUILD_SLICE: DECODE_UINT; - printf("BUILD_SLICE " UINT_FMT, unum); + mp_printf(print, "BUILD_SLICE " UINT_FMT, unum); break; #endif case MP_BC_STORE_COMP: DECODE_UINT; - printf("STORE_COMP " UINT_FMT, unum); + mp_printf(print, "STORE_COMP " UINT_FMT, unum); break; case MP_BC_UNPACK_SEQUENCE: DECODE_UINT; - printf("UNPACK_SEQUENCE " UINT_FMT, unum); + mp_printf(print, "UNPACK_SEQUENCE " UINT_FMT, unum); break; case MP_BC_UNPACK_EX: DECODE_UINT; - printf("UNPACK_EX " UINT_FMT, unum); + mp_printf(print, "UNPACK_EX " UINT_FMT, unum); break; case MP_BC_MAKE_FUNCTION: DECODE_PTR; - printf("MAKE_FUNCTION %p", (void *)(uintptr_t)unum); + mp_printf(print, "MAKE_FUNCTION %p", (void *)(uintptr_t)unum); break; case MP_BC_MAKE_FUNCTION_DEFARGS: DECODE_PTR; - printf("MAKE_FUNCTION_DEFARGS %p", (void *)(uintptr_t)unum); + mp_printf(print, "MAKE_FUNCTION_DEFARGS %p", (void *)(uintptr_t)unum); break; case MP_BC_MAKE_CLOSURE: { DECODE_PTR; mp_uint_t n_closed_over = *ip++; - printf("MAKE_CLOSURE %p " UINT_FMT, (void *)(uintptr_t)unum, n_closed_over); + mp_printf(print, "MAKE_CLOSURE %p " UINT_FMT, (void *)(uintptr_t)unum, n_closed_over); break; } case MP_BC_MAKE_CLOSURE_DEFARGS: { DECODE_PTR; mp_uint_t n_closed_over = *ip++; - printf("MAKE_CLOSURE_DEFARGS %p " UINT_FMT, (void *)(uintptr_t)unum, n_closed_over); + mp_printf(print, "MAKE_CLOSURE_DEFARGS %p " UINT_FMT, (void *)(uintptr_t)unum, n_closed_over); break; } case MP_BC_CALL_FUNCTION: DECODE_UINT; - printf("CALL_FUNCTION n=" UINT_FMT " nkw=" UINT_FMT, unum & 0xff, (unum >> 8) & 0xff); + mp_printf(print, "CALL_FUNCTION n=" UINT_FMT " nkw=" UINT_FMT, unum & 0xff, (unum >> 8) & 0xff); break; case MP_BC_CALL_FUNCTION_VAR_KW: DECODE_UINT; - printf("CALL_FUNCTION_VAR_KW n=" UINT_FMT " nkw=" UINT_FMT, unum & 0xff, (unum >> 8) & 0xff); + mp_printf(print, "CALL_FUNCTION_VAR_KW n=" UINT_FMT " nkw=" UINT_FMT, unum & 0xff, (unum >> 8) & 0xff); break; case MP_BC_CALL_METHOD: DECODE_UINT; - printf("CALL_METHOD n=" UINT_FMT " nkw=" UINT_FMT, unum & 0xff, (unum >> 8) & 0xff); + mp_printf(print, "CALL_METHOD n=" UINT_FMT " nkw=" UINT_FMT, unum & 0xff, (unum >> 8) & 0xff); break; case MP_BC_CALL_METHOD_VAR_KW: DECODE_UINT; - printf("CALL_METHOD_VAR_KW n=" UINT_FMT " nkw=" UINT_FMT, unum & 0xff, (unum >> 8) & 0xff); + mp_printf(print, "CALL_METHOD_VAR_KW n=" UINT_FMT " nkw=" UINT_FMT, unum & 0xff, (unum >> 8) & 0xff); break; case MP_BC_RETURN_VALUE: - printf("RETURN_VALUE"); + mp_printf(print, "RETURN_VALUE"); break; case MP_BC_RAISE_LAST: - printf("RAISE_LAST"); + mp_printf(print, "RAISE_LAST"); break; case MP_BC_RAISE_OBJ: - printf("RAISE_OBJ"); + mp_printf(print, "RAISE_OBJ"); break; case MP_BC_RAISE_FROM: - printf("RAISE_FROM"); + mp_printf(print, "RAISE_FROM"); break; case MP_BC_YIELD_VALUE: - printf("YIELD_VALUE"); + mp_printf(print, "YIELD_VALUE"); break; case MP_BC_YIELD_FROM: - printf("YIELD_FROM"); + mp_printf(print, "YIELD_FROM"); break; case MP_BC_IMPORT_NAME: DECODE_QSTR; - printf("IMPORT_NAME '%s'", qstr_str(qst)); + mp_printf(print, "IMPORT_NAME '%s'", qstr_str(qst)); break; case MP_BC_IMPORT_FROM: DECODE_QSTR; - printf("IMPORT_FROM '%s'", qstr_str(qst)); + mp_printf(print, "IMPORT_FROM '%s'", qstr_str(qst)); break; case MP_BC_IMPORT_STAR: - printf("IMPORT_STAR"); + mp_printf(print, "IMPORT_STAR"); break; default: if (ip[-1] < MP_BC_LOAD_CONST_SMALL_INT_MULTI + 64) { - printf("LOAD_CONST_SMALL_INT " INT_FMT, (mp_int_t)ip[-1] - MP_BC_LOAD_CONST_SMALL_INT_MULTI - 16); + mp_printf(print, "LOAD_CONST_SMALL_INT " INT_FMT, (mp_int_t)ip[-1] - MP_BC_LOAD_CONST_SMALL_INT_MULTI - 16); } else if (ip[-1] < MP_BC_LOAD_FAST_MULTI + 16) { - printf("LOAD_FAST " UINT_FMT, (mp_uint_t)ip[-1] - MP_BC_LOAD_FAST_MULTI); + mp_printf(print, "LOAD_FAST " UINT_FMT, (mp_uint_t)ip[-1] - MP_BC_LOAD_FAST_MULTI); } else if (ip[-1] < MP_BC_STORE_FAST_MULTI + 16) { - printf("STORE_FAST " UINT_FMT, (mp_uint_t)ip[-1] - MP_BC_STORE_FAST_MULTI); + mp_printf(print, "STORE_FAST " UINT_FMT, (mp_uint_t)ip[-1] - MP_BC_STORE_FAST_MULTI); } else if (ip[-1] < MP_BC_UNARY_OP_MULTI + MP_UNARY_OP_NUM_BYTECODE) { - printf("UNARY_OP " UINT_FMT, (mp_uint_t)ip[-1] - MP_BC_UNARY_OP_MULTI); + mp_printf(print, "UNARY_OP " UINT_FMT, (mp_uint_t)ip[-1] - MP_BC_UNARY_OP_MULTI); } else if (ip[-1] < MP_BC_BINARY_OP_MULTI + MP_BINARY_OP_NUM_BYTECODE) { mp_uint_t op = ip[-1] - MP_BC_BINARY_OP_MULTI; - printf("BINARY_OP " UINT_FMT " %s", op, qstr_str(mp_binary_op_method_name[op])); + mp_printf(print, "BINARY_OP " UINT_FMT " %s", op, qstr_str(mp_binary_op_method_name[op])); } else { - printf("code %p, byte code 0x%02x not implemented\n", ip - 1, ip[-1]); + mp_printf(print, "code %p, byte code 0x%02x not implemented\n", ip - 1, ip[-1]); assert(0); return ip; } @@ -549,13 +546,13 @@ const byte *mp_bytecode_print_str(const byte *ip) { return ip; } -void mp_bytecode_print2(const byte *ip, size_t len, const mp_uint_t *const_table) { +void mp_bytecode_print2(const mp_print_t *print, const byte *ip, size_t len, const mp_uint_t *const_table) { mp_showbc_code_start = ip; mp_showbc_const_table = const_table; while (ip < len + mp_showbc_code_start) { - printf("%02u ", (uint)(ip - mp_showbc_code_start)); - ip = mp_bytecode_print_str(ip); - printf("\n"); + mp_printf(print, "%02u ", (uint)(ip - mp_showbc_code_start)); + ip = mp_bytecode_print_str(print, ip); + mp_printf(print, "\n"); } } diff --git a/py/vm.c b/py/vm.c index f30aed8ff1..393a2bbbd6 100644 --- a/py/vm.c +++ b/py/vm.c @@ -39,7 +39,7 @@ // *FORMAT-OFF* #if 0 -#define TRACE(ip) printf("sp=%d ", (int)(sp - &code_state->state[0] + 1)); mp_bytecode_print2(ip, 1, code_state->fun_bc->const_table); +#define TRACE(ip) printf("sp=%d ", (int)(sp - &code_state->state[0] + 1)); mp_bytecode_print2(&mp_plat_print, ip, 1, code_state->fun_bc->const_table); #else #define TRACE(ip) #endif From acdb0608b7622cc7af0bdbfc89b2696583c948ae Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 11 Sep 2020 23:00:03 +1000 Subject: [PATCH 047/337] py/parse: Pass in an mp_print_t to mp_parse_node_print. So the output can be redirected if needed. Signed-off-by: Damien George --- ports/unix/main.c | 2 +- py/parse.c | 36 ++++++++++++++++++------------------ py/parse.h | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/ports/unix/main.c b/ports/unix/main.c index c38b7b0c28..0fe492a554 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -133,7 +133,7 @@ STATIC int execute_from_lexer(int source_kind, const void *source, mp_parse_inpu // allow to print the parse tree in the coverage build if (mp_verbose_flag >= 3) { printf("----------------\n"); - mp_parse_node_print(parse_tree.root, 0); + mp_parse_node_print(&mp_plat_print, parse_tree.root, 0); printf("----------------\n"); } #endif diff --git a/py/parse.c b/py/parse.c index b93282165f..38cd215a9f 100644 --- a/py/parse.c +++ b/py/parse.c @@ -368,35 +368,35 @@ size_t mp_parse_node_extract_list(mp_parse_node_t *pn, size_t pn_kind, mp_parse_ } #if MICROPY_DEBUG_PRINTERS -void mp_parse_node_print(mp_parse_node_t pn, size_t indent) { +void mp_parse_node_print(const mp_print_t *print, mp_parse_node_t pn, size_t indent) { if (MP_PARSE_NODE_IS_STRUCT(pn)) { - printf("[% 4d] ", (int)((mp_parse_node_struct_t *)pn)->source_line); + mp_printf(print, "[% 4d] ", (int)((mp_parse_node_struct_t *)pn)->source_line); } else { - printf(" "); + mp_printf(print, " "); } for (size_t i = 0; i < indent; i++) { - printf(" "); + mp_printf(print, " "); } if (MP_PARSE_NODE_IS_NULL(pn)) { - printf("NULL\n"); + mp_printf(print, "NULL\n"); } else if (MP_PARSE_NODE_IS_SMALL_INT(pn)) { mp_int_t arg = MP_PARSE_NODE_LEAF_SMALL_INT(pn); - printf("int(" INT_FMT ")\n", arg); + mp_printf(print, "int(" INT_FMT ")\n", arg); } else if (MP_PARSE_NODE_IS_LEAF(pn)) { uintptr_t arg = MP_PARSE_NODE_LEAF_ARG(pn); switch (MP_PARSE_NODE_LEAF_KIND(pn)) { case MP_PARSE_NODE_ID: - printf("id(%s)\n", qstr_str(arg)); + mp_printf(print, "id(%s)\n", qstr_str(arg)); break; case MP_PARSE_NODE_STRING: - printf("str(%s)\n", qstr_str(arg)); + mp_printf(print, "str(%s)\n", qstr_str(arg)); break; case MP_PARSE_NODE_BYTES: - printf("bytes(%s)\n", qstr_str(arg)); + mp_printf(print, "bytes(%s)\n", qstr_str(arg)); break; default: assert(MP_PARSE_NODE_LEAF_KIND(pn) == MP_PARSE_NODE_TOKEN); - printf("tok(%u)\n", (uint)arg); + mp_printf(print, "tok(%u)\n", (uint)arg); break; } } else { @@ -404,19 +404,19 @@ void mp_parse_node_print(mp_parse_node_t pn, size_t indent) { mp_parse_node_struct_t *pns = (mp_parse_node_struct_t *)pn; if (MP_PARSE_NODE_STRUCT_KIND(pns) == RULE_const_object) { #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D - printf("literal const(%016llx)\n", (uint64_t)pns->nodes[0] | ((uint64_t)pns->nodes[1] << 32)); + mp_printf(print, "literal const(%016llx)\n", (uint64_t)pns->nodes[0] | ((uint64_t)pns->nodes[1] << 32)); #else - printf("literal const(%p)\n", (mp_obj_t)pns->nodes[0]); + mp_printf(print, "literal const(%p)\n", (mp_obj_t)pns->nodes[0]); #endif } else { size_t n = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); #if USE_RULE_NAME - printf("%s(%u) (n=%u)\n", rule_name_table[MP_PARSE_NODE_STRUCT_KIND(pns)], (uint)MP_PARSE_NODE_STRUCT_KIND(pns), (uint)n); + mp_printf(print, "%s(%u) (n=%u)\n", rule_name_table[MP_PARSE_NODE_STRUCT_KIND(pns)], (uint)MP_PARSE_NODE_STRUCT_KIND(pns), (uint)n); #else - printf("rule(%u) (n=%u)\n", (uint)MP_PARSE_NODE_STRUCT_KIND(pns), (uint)n); + mp_printf(print, "rule(%u) (n=%u)\n", (uint)MP_PARSE_NODE_STRUCT_KIND(pns), (uint)n); #endif for (size_t i = 0; i < n; i++) { - mp_parse_node_print(pns->nodes[i], indent + 2); + mp_parse_node_print(print, pns->nodes[i], indent + 2); } } } @@ -424,10 +424,10 @@ void mp_parse_node_print(mp_parse_node_t pn, size_t indent) { #endif // MICROPY_DEBUG_PRINTERS /* -STATIC void result_stack_show(parser_t *parser) { - printf("result stack, most recent first\n"); +STATIC void result_stack_show(const mp_print_t *print, parser_t *parser) { + mp_printf(print, "result stack, most recent first\n"); for (ssize_t i = parser->result_stack_top - 1; i >= 0; i--) { - mp_parse_node_print(parser->result_stack[i], 0); + mp_parse_node_print(print, parser->result_stack[i], 0); } } */ diff --git a/py/parse.h b/py/parse.h index 4ed8a7ad12..a6eb380047 100644 --- a/py/parse.h +++ b/py/parse.h @@ -86,7 +86,7 @@ bool mp_parse_node_is_const_false(mp_parse_node_t pn); bool mp_parse_node_is_const_true(mp_parse_node_t pn); bool mp_parse_node_get_int_maybe(mp_parse_node_t pn, mp_obj_t *o); size_t mp_parse_node_extract_list(mp_parse_node_t *pn, size_t pn_kind, mp_parse_node_t **nodes); -void mp_parse_node_print(mp_parse_node_t pn, size_t indent); +void mp_parse_node_print(const mp_print_t *print, mp_parse_node_t pn, size_t indent); typedef enum { MP_PARSE_SINGLE_INPUT, From b31cb21a3936b97af4a52b082e1f40c2061aeab7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 12 Sep 2020 13:47:59 +1000 Subject: [PATCH 048/337] stm32/servo: Fix angle and speed methods to work again with -ve args. Fixes a regression introduced by 70affd9ba22e7f62666a9a2fafc2a3c0be9ef95a Fixes issue #6403 Signed-off-by: Damien George --- ports/stm32/servo.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/servo.c b/ports/stm32/servo.c index a347c6bbd0..1708422463 100644 --- a/ports/stm32/servo.c +++ b/ports/stm32/servo.c @@ -278,7 +278,7 @@ STATIC mp_obj_t pyb_servo_angle(size_t n_args, const mp_obj_t *args) { return mp_obj_new_int((self->pulse_cur - self->pulse_centre) * 90 / self->pulse_angle_90); } else { #if MICROPY_PY_BUILTINS_FLOAT - self->pulse_dest = self->pulse_centre + (uint16_t)((mp_float_t)self->pulse_angle_90 * mp_obj_get_float(args[1]) / MICROPY_FLOAT_CONST(90.0)); + self->pulse_dest = self->pulse_centre + (int16_t)((mp_float_t)self->pulse_angle_90 * mp_obj_get_float(args[1]) / MICROPY_FLOAT_CONST(90.0)); #else self->pulse_dest = self->pulse_centre + self->pulse_angle_90 * mp_obj_get_int(args[1]) / 90; #endif @@ -308,7 +308,7 @@ STATIC mp_obj_t pyb_servo_speed(size_t n_args, const mp_obj_t *args) { return mp_obj_new_int((self->pulse_cur - self->pulse_centre) * 100 / self->pulse_speed_100); } else { #if MICROPY_PY_BUILTINS_FLOAT - self->pulse_dest = self->pulse_centre + (uint16_t)((mp_float_t)self->pulse_speed_100 * mp_obj_get_float(args[1]) / MICROPY_FLOAT_CONST(100.0)); + self->pulse_dest = self->pulse_centre + (int16_t)((mp_float_t)self->pulse_speed_100 * mp_obj_get_float(args[1]) / MICROPY_FLOAT_CONST(100.0)); #else self->pulse_dest = self->pulse_centre + self->pulse_speed_100 * mp_obj_get_int(args[1]) / 100; #endif From 504522bd02f9d9f9848d5a6e38427df3d1e4a6e3 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 11 Sep 2020 10:53:09 +1000 Subject: [PATCH 049/337] extmod/modbluetooth: Fix handling of optional data/uuid args. For the following 3 functions, previously the code relied on whether the arg was passed at all, to make it optional. Now it allows them to be explicitly `None` to indicate they are not used: - gatts_notify(..., [data]) - gattc_discover_services(..., [uuid]) - gattc_discover_characteristics(..., [uuid]) Also ensure that the uuid arguments are actually instances of the uuid type, and fix handling of the 5th arg in gattc_discover_characteristics(). --- extmod/modbluetooth.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index a8a762ce32..14d0921c84 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -679,7 +679,7 @@ STATIC mp_obj_t bluetooth_ble_gatts_notify(size_t n_args, const mp_obj_t *args) mp_int_t conn_handle = mp_obj_get_int(args[1]); mp_int_t value_handle = mp_obj_get_int(args[2]); - if (n_args == 4) { + if (n_args == 4 && args[3] != mp_const_none) { mp_buffer_info_t bufinfo = {0}; mp_get_buffer_raise(args[3], &bufinfo, MP_BUFFER_READ); int err = mp_bluetooth_gatts_notify_send(conn_handle, value_handle, bufinfo.buf, bufinfo.len); @@ -719,7 +719,10 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gatts_set_buffer_obj, 3 STATIC mp_obj_t bluetooth_ble_gattc_discover_services(size_t n_args, const mp_obj_t *args) { mp_int_t conn_handle = mp_obj_get_int(args[1]); mp_obj_bluetooth_uuid_t *uuid = NULL; - if (n_args == 3) { + if (n_args == 3 && args[2] != mp_const_none) { + if (!mp_obj_is_type(args[2], &bluetooth_uuid_type)) { + mp_raise_TypeError(MP_ERROR_TEXT("UUID")); + } uuid = MP_OBJ_TO_PTR(args[2]); } return bluetooth_handle_errno(mp_bluetooth_gattc_discover_primary_services(conn_handle, uuid)); @@ -731,7 +734,10 @@ STATIC mp_obj_t bluetooth_ble_gattc_discover_characteristics(size_t n_args, cons mp_int_t start_handle = mp_obj_get_int(args[2]); mp_int_t end_handle = mp_obj_get_int(args[3]); mp_obj_bluetooth_uuid_t *uuid = NULL; - if (n_args == 3) { + if (n_args == 5 && args[4] != mp_const_none) { + if (!mp_obj_is_type(args[4], &bluetooth_uuid_type)) { + mp_raise_TypeError(MP_ERROR_TEXT("UUID")); + } uuid = MP_OBJ_TO_PTR(args[4]); } return bluetooth_handle_errno(mp_bluetooth_gattc_discover_characteristics(conn_handle, start_handle, end_handle, uuid)); From 6a6a5f9e151473bdcc1d14725d680691ff665a82 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 11 Sep 2020 11:43:53 +1000 Subject: [PATCH 050/337] extmod/modbluetooth: Make BLE.irq() method positional only. Simplifcation now that the trigger arg has been removed. --- extmod/modbluetooth.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 14d0921c84..64cd383525 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -384,27 +384,21 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_ble_config_obj, 1, bluetooth_ble_config); -STATIC mp_obj_t bluetooth_ble_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_handler }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_handler, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_NONE} }, - }; - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - mp_obj_t callback = args[ARG_handler].u_obj; - if (callback != mp_const_none && !mp_obj_is_callable(callback)) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid callback")); +STATIC mp_obj_t bluetooth_ble_irq(mp_obj_t self_in, mp_obj_t handler_in) { + (void)self_in; + if (handler_in != mp_const_none && !mp_obj_is_callable(handler_in)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid handler")); } // Update the callback. MICROPY_PY_BLUETOOTH_ENTER mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); - o->irq_handler = callback; + o->irq_handler = handler_in; MICROPY_PY_BLUETOOTH_EXIT return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_ble_irq_obj, 1, bluetooth_ble_irq); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_irq_obj, bluetooth_ble_irq); // ---------------------------------------------------------------------------- // Bluetooth object: GAP From 19faf550904c4e93427450994b7babe367eb9add Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 11 Sep 2020 11:45:09 +1000 Subject: [PATCH 051/337] docs/library/ubluetooth.rst: Clarify position/kw arguments. Almost all ubluetooth functions are positional-only, but some have optional args. Make this clearer and show what the defaults are. --- docs/library/ubluetooth.rst | 40 ++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/library/ubluetooth.rst b/docs/library/ubluetooth.rst index f8dad7b8d1..5635f4d69a 100644 --- a/docs/library/ubluetooth.rst +++ b/docs/library/ubluetooth.rst @@ -28,15 +28,15 @@ Constructor Configuration ------------- -.. method:: BLE.active([active]) +.. method:: BLE.active([active], /) Optionally changes the active state of the BLE radio, and returns the current state. The radio must be made active before using any other methods on this class. -.. method:: BLE.config('param') - BLE.config(param=value, ...) +.. method:: BLE.config('param', /) + BLE.config(*, param=value, ...) Get or set configuration values of the BLE interface. To get a value the parameter name should be quoted as a string, and just one parameter is @@ -76,7 +76,7 @@ Configuration Event Handling -------------- -.. method:: BLE.irq(handler) +.. method:: BLE.irq(handler, /) Registers a callback for events from the BLE stack. The *handler* takes two arguments, ``event`` (which will be one of the codes below) and ``data`` @@ -195,7 +195,7 @@ program. Broadcaster Role (Advertiser) ----------------------------- -.. method:: BLE.gap_advertise(interval_us, adv_data=None, resp_data=None, connectable=True) +.. method:: BLE.gap_advertise(interval_us, adv_data=None, *, resp_data=None, connectable=True) Starts advertising at the specified interval (in **micro**\ seconds). This interval will be rounded down to the nearest 625us. To stop advertising, set @@ -214,7 +214,7 @@ Broadcaster Role (Advertiser) Observer Role (Scanner) ----------------------- -.. method:: BLE.gap_scan(duration_ms, [interval_us], [window_us], [active]) +.. method:: BLE.gap_scan(duration_ms, interval_us=1280000, window_us=11250, active=False, /) Run a scan operation lasting for the specified duration (in **milli**\ seconds). @@ -244,7 +244,7 @@ Observer Role (Scanner) * 0x04 - SCAN_RSP - scan response ``active`` can be set ``True`` if you want to receive scan responses in the results. - + When scanning is stopped (either due to the duration finishing or when explicitly stopped), the ``_IRQ_SCAN_DONE`` event will be raised. @@ -268,7 +268,7 @@ writes from a central to a given characteristic, use :meth:`gatts_write` after registration. e.g. ``gatts_write(char_handle, bytes(100))``. -.. method:: BLE.gatts_register_services(services_definition) +.. method:: BLE.gatts_register_services(services_definition, /) Configures the peripheral with the specified services, replacing any existing services. @@ -308,26 +308,26 @@ writes from a central to a given characteristic, use **Note:** Advertising must be stopped before registering services. -.. method:: BLE.gatts_read(value_handle) +.. method:: BLE.gatts_read(value_handle, /) Reads the local value for this handle (which has either been written by :meth:`gatts_write ` or by a remote central). -.. method:: BLE.gatts_write(value_handle, data) +.. method:: BLE.gatts_write(value_handle, data, /) Writes the local value for this handle, which can be read by a central. -.. method:: BLE.gatts_notify(conn_handle, value_handle, [data]) +.. method:: BLE.gatts_notify(conn_handle, value_handle, data=None, /) Sends a notification request to a connected central. - If *data* is specified, then that value is sent to the central as part of + If *data* is not ``None``, then that value is sent to the central as part of the notification. The local value will not be modified. - Otherwise, if *data* is not specified, then the current local value (as + Otherwise, if *data* is ``None``, then the current local value (as set with :meth:`gatts_write `) will be sent. -.. method:: BLE.gatts_indicate(conn_handle, value_handle) +.. method:: BLE.gatts_indicate(conn_handle, value_handle, /) Sends an indication request to a connected central. @@ -361,7 +361,7 @@ Central Role (GATT Client) On success, the ``_IRQ_PERIPHERAL_CONNECT`` event will be raised. -.. method:: BLE.gap_disconnect(conn_handle) +.. method:: BLE.gap_disconnect(conn_handle, /) Disconnect the specified connection handle. @@ -370,7 +370,7 @@ Central Role (GATT Client) Returns ``False`` if the connection handle wasn't connected, and ``True`` otherwise. -.. method:: BLE.gattc_discover_services(conn_handle, [uuid]) +.. method:: BLE.gattc_discover_services(conn_handle, uuid=None, /) Query a connected peripheral for its services. @@ -379,7 +379,7 @@ Central Role (GATT Client) For each service discovered, the ``_IRQ_GATTC_SERVICE_RESULT`` event will be raised, followed by ``_IRQ_GATTC_SERVICE_DONE`` on completion. -.. method:: BLE.gattc_discover_characteristics(conn_handle, start_handle, end_handle, [uuid]) +.. method:: BLE.gattc_discover_characteristics(conn_handle, start_handle, end_handle, uuid=None, /) Query a connected peripheral for characteristics in the specified range. @@ -392,14 +392,14 @@ Central Role (GATT Client) For each characteristic discovered, the ``_IRQ_GATTC_CHARACTERISTIC_RESULT`` event will be raised, followed by ``_IRQ_GATTC_CHARACTERISTIC_DONE`` on completion. -.. method:: BLE.gattc_discover_descriptors(conn_handle, start_handle, end_handle) +.. method:: BLE.gattc_discover_descriptors(conn_handle, start_handle, end_handle, /) Query a connected peripheral for descriptors in the specified range. For each descriptor discovered, the ``_IRQ_GATTC_DESCRIPTOR_RESULT`` event will be raised, followed by ``_IRQ_GATTC_DESCRIPTOR_DONE`` on completion. -.. method:: BLE.gattc_read(conn_handle, value_handle) +.. method:: BLE.gattc_read(conn_handle, value_handle, /) Issue a remote read to a connected peripheral for the specified characteristic or descriptor handle. @@ -433,7 +433,7 @@ class UUID Constructor ----------- -.. class:: UUID(value) +.. class:: UUID(value, /) Creates a UUID instance with the specified **value**. From 5be3bfcb7e065bd95eb8460d2069524b934bcd0f Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 15 Sep 2020 11:38:58 +1000 Subject: [PATCH 052/337] extmod/modbluetooth: Print UUIDs correctly. In particular, the printed string can now be re-evaluated to construct the same UUID object. --- extmod/modbluetooth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index 64cd383525..f77c7eb63c 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -181,7 +181,7 @@ STATIC void bluetooth_uuid_print(const mp_print_t *print, mp_obj_t self_in, mp_p (void)kind; mp_obj_bluetooth_uuid_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "UUID%u(%s", self->type * 8, self->type <= 4 ? "0x" : "'"); + mp_printf(print, "UUID(%s", self->type <= 4 ? "0x" : "'"); for (int i = 0; i < self->type; ++i) { if (i == 4 || i == 6 || i == 8 || i == 10) { mp_printf(print, "-"); From c200759290bae8ceb74c91d29c0a521292d925f4 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 11 Sep 2020 12:26:09 +1000 Subject: [PATCH 053/337] docs/library/ubluetooth.rst: Clarify peripheral/central vs server/client Previously this documentation assumed peripheral=server and central=client, but this is not accurate or true to the implementation. --- docs/library/ubluetooth.rst | 130 +++++++++++++++++++++++------------- 1 file changed, 82 insertions(+), 48 deletions(-) diff --git a/docs/library/ubluetooth.rst b/docs/library/ubluetooth.rst index 5635f4d69a..e4471f8054 100644 --- a/docs/library/ubluetooth.rst +++ b/docs/library/ubluetooth.rst @@ -6,8 +6,8 @@ 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, and a device may operate in multiple -roles concurrently. +Broadcaster, and Observer roles, as well as GATT Server and Client. 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. @@ -70,8 +70,7 @@ Configuration incoming events. This buffer is global to the entire BLE driver and so handles incoming data for all events, including all characteristics. Increasing this allows better handling of bursty incoming data (for - example scan results) and the ability for a central role to receive - larger characteristic values. + example scan results) and the ability to receive larger characteristic values. Event Handling -------------- @@ -99,10 +98,10 @@ Event Handling # A central has disconnected from this peripheral. conn_handle, addr_type, addr = data elif event == _IRQ_GATTS_WRITE: - # A central has written to this characteristic or descriptor. + # A client has written to this characteristic or descriptor. conn_handle, attr_handle = data elif event == _IRQ_GATTS_READ_REQUEST: - # A central has issued a read. Note: this is a hard IRQ. + # 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. conn_handle, attr_handle = data @@ -153,13 +152,13 @@ Event Handling # Note: Status will be zero on success, implementation-specific value otherwise. conn_handle, value_handle, status = data elif event == _IRQ_GATTC_NOTIFY: - # A peripheral has sent a notify request. + # A server has sent a notify request. conn_handle, value_handle, notify_data = data elif event == _IRQ_GATTC_INDICATE: - # A peripheral has sent an indicate request. + # A server has sent an indicate request. conn_handle, value_handle, notify_data = data elif event == _IRQ_GATTS_INDICATE_DONE: - # A central has acknowledged the indication. + # A client has acknowledged the indication. # Note: Status will be zero on successful acknowledgment, implementation-specific value otherwise. conn_handle, value_handle, status = data @@ -249,28 +248,74 @@ Observer Role (Scanner) explicitly stopped), the ``_IRQ_SCAN_DONE`` event will be raised. -Peripheral Role (GATT Server) ------------------------------ +Central Role +------------ -A BLE peripheral has a set of registered services. Each service may contain +A central device can connect to peripherals that it has discovered using the observer role (see :meth:`gap_scan`) or with a known address. + +.. method:: BLE.gap_connect(addr_type, addr, scan_duration_ms=2000, /) + + Connect to a peripheral. + + See :meth:`gap_scan ` for details about address types. + + On success, the ``_IRQ_PERIPHERAL_CONNECT`` event will be raised. + + +Peripheral Role +--------------- + +A peripheral device is expected to send connectable advertisements (see +:meth:`gap_advertise`). It will usually be acting as a GATT +server, having first registered services and characteristics using +:meth:`gatts_register_services`. + +When a central connects, the ``_IRQ_CENTRAL_CONNECT`` event will be raised. + + +Central & Peripheral Roles +-------------------------- + +.. method:: BLE.gap_disconnect(conn_handle, /) + + Disconnect the specified connection handle. This can either be a + central that has connected to this device (if acting as a peripheral) + or a peripheral that was previously connected to by this device (if acting + as a central). + + On success, the ``_IRQ_PERIPHERAL_DISCONNECT`` or ``_IRQ_CENTRAL_DISCONNECT`` + event will be raised. + + Returns ``False`` if the connection handle wasn't connected, and ``True`` + otherwise. + + +GATT Server +----------- + +A GATT server has a set of registered services. Each service may contain characteristics, which each have a value. Characteristics can also contain descriptors, which themselves have values. These values are stored locally, and are accessed by their "value handle" which is generated during service registration. They can also be read from or written -to by a remote central device. Additionally, a peripheral can "notify" a -characteristic to a connected central via a connection handle. +to by a remote client device. Additionally, a server can "notify" a +characteristic to a connected client via a connection handle. + +A device in either central or peripheral roles may function as a GATT server, +however in most cases it will be more common for a peripheral device to act +as the server. Characteristics and descriptors have a default maximum size of 20 bytes. -Anything written to them by a central will be truncated to this length. However, +Anything written to them by a client will be truncated to this length. However, any local write will increase the maximum size, so if you want to allow larger -writes from a central to a given characteristic, use +writes from a client to a given characteristic, use :meth:`gatts_write` after registration. e.g. ``gatts_write(char_handle, bytes(100))``. .. method:: BLE.gatts_register_services(services_definition, /) - Configures the peripheral with the specified services, replacing any + Configures the server with the specified services, replacing any existing services. *services_definition* is a list of **services**, where each **service** is a @@ -311,17 +356,17 @@ writes from a central to a given characteristic, use .. method:: BLE.gatts_read(value_handle, /) Reads the local value for this handle (which has either been written by - :meth:`gatts_write ` or by a remote central). + :meth:`gatts_write ` or by a remote client). .. method:: BLE.gatts_write(value_handle, data, /) - Writes the local value for this handle, which can be read by a central. + Writes the local value for this handle, which can be read by a client. .. method:: BLE.gatts_notify(conn_handle, value_handle, data=None, /) - Sends a notification request to a connected central. + Sends a notification request to a connected client. - If *data* is not ``None``, then that value is sent to the central as part of + If *data* is not ``None``, then that value is sent to the client as part of the notification. The local value will not be modified. Otherwise, if *data* is ``None``, then the current local value (as @@ -329,7 +374,7 @@ writes from a central to a given characteristic, use .. method:: BLE.gatts_indicate(conn_handle, value_handle, /) - Sends an indication request to a connected central. + Sends an indication request to a connected client. **Note:** This does not currently support sending a custom value, it will always send the current local value (as set with :meth:`gatts_write @@ -349,30 +394,19 @@ writes from a central to a given characteristic, use be cleared after reading. This feature is useful when implementing something like the Nordic UART Service. +GATT Client +----------- -Central Role (GATT Client) --------------------------- +A GATT client can discover and read/write characteristics on a remote GATT server. -.. method:: BLE.gap_connect(addr_type, addr, scan_duration_ms=2000, /) - - Connect to a peripheral. - - See :meth:`gatts_write ` for details about address types. - - On success, the ``_IRQ_PERIPHERAL_CONNECT`` event will be raised. - -.. method:: BLE.gap_disconnect(conn_handle, /) - - Disconnect the specified connection handle. - - On success, the ``_IRQ_PERIPHERAL_DISCONNECT`` event will be raised. - - Returns ``False`` if the connection handle wasn't connected, and ``True`` - otherwise. +It is more common for a central role device to act as the GATT client, however +it's also possible for a peripheral to act as a client in order to discover +information about the central that has connected to it (e.g. to read the +device name from the device information service). .. method:: BLE.gattc_discover_services(conn_handle, uuid=None, /) - Query a connected peripheral for its services. + Query a connected server for its services. Optionally specify a service *uuid* to query for that service only. @@ -381,7 +415,7 @@ Central Role (GATT Client) .. method:: BLE.gattc_discover_characteristics(conn_handle, start_handle, end_handle, uuid=None, /) - Query a connected peripheral for characteristics in the specified range. + Query a connected server for characteristics in the specified range. Optionally specify a characteristic *uuid* to query for that characteristic only. @@ -394,14 +428,14 @@ Central Role (GATT Client) .. method:: BLE.gattc_discover_descriptors(conn_handle, start_handle, end_handle, /) - Query a connected peripheral for descriptors in the specified range. + Query a connected server for descriptors in the specified range. For each descriptor discovered, the ``_IRQ_GATTC_DESCRIPTOR_RESULT`` event will be raised, followed by ``_IRQ_GATTC_DESCRIPTOR_DONE`` on completion. .. method:: BLE.gattc_read(conn_handle, value_handle, /) - Issue a remote read to a connected peripheral for the specified + Issue a remote read to a connected server for the specified characteristic or descriptor handle. When a value is available, the ``_IRQ_GATTC_READ_RESULT`` event will be @@ -409,20 +443,20 @@ Central Role (GATT Client) .. method:: BLE.gattc_write(conn_handle, value_handle, data, mode=0, /) - Issue a remote write to a connected peripheral for the specified + Issue a remote write to a connected server for the specified characteristic or descriptor handle. The argument *mode* specifies the write behaviour, with the currently supported values being: * ``mode=0`` (default) is a write-without-response: the write will - be sent to the remote peripheral but no confirmation will be + be sent to the remote server but no confirmation will be returned, and no event will be raised. - * ``mode=1`` is a write-with-response: the remote peripheral is + * ``mode=1`` is a write-with-response: the remote server is requested to send a response/acknowledgement that it received the data. - If a response is received from the remote peripheral the + If a response is received from the remote server the ``_IRQ_GATTC_WRITE_DONE`` event will be raised. From fe642ced43809f932eaa8e3396c8bd0f8265de09 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 18 Sep 2020 12:50:05 +1000 Subject: [PATCH 054/337] tests/multi_bluetooth: Update UUID format in .exp files. --- tests/multi_bluetooth/ble_characteristic.py.exp | 2 +- tests/multi_bluetooth/ble_gap_device_name.py.exp | 2 +- tests/multi_bluetooth/ble_gatt_data_transfer.py.exp | 6 +++--- tests/multi_bluetooth/ble_gattc_discover_services.py.exp | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/multi_bluetooth/ble_characteristic.py.exp b/tests/multi_bluetooth/ble_characteristic.py.exp index 25b5544efc..90dc46c06f 100644 --- a/tests/multi_bluetooth/ble_characteristic.py.exp +++ b/tests/multi_bluetooth/ble_characteristic.py.exp @@ -14,7 +14,7 @@ _IRQ_CENTRAL_DISCONNECT --- instance1 --- gap_connect _IRQ_PERIPHERAL_CONNECT -_IRQ_GATTC_CHARACTERISTIC_RESULT UUID128('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') gattc_read _IRQ_GATTC_READ_RESULT b'periph0' _IRQ_GATTC_READ_DONE 0 diff --git a/tests/multi_bluetooth/ble_gap_device_name.py.exp b/tests/multi_bluetooth/ble_gap_device_name.py.exp index 377df90dee..0f4ee6e3dc 100644 --- a/tests/multi_bluetooth/ble_gap_device_name.py.exp +++ b/tests/multi_bluetooth/ble_gap_device_name.py.exp @@ -11,7 +11,7 @@ _IRQ_CENTRAL_DISCONNECT gap_connect _IRQ_PERIPHERAL_CONNECT gattc_discover_characteristics -_IRQ_GATTC_CHARACTERISTIC_RESULT UUID16(0x2a00) +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID(0x2a00) gattc_read _IRQ_GATTC_READ_RESULT b'GAP_NAME0' gap_disconnect: True diff --git a/tests/multi_bluetooth/ble_gatt_data_transfer.py.exp b/tests/multi_bluetooth/ble_gatt_data_transfer.py.exp index a1b3cbcd07..94a0718108 100644 --- a/tests/multi_bluetooth/ble_gatt_data_transfer.py.exp +++ b/tests/multi_bluetooth/ble_gatt_data_transfer.py.exp @@ -11,9 +11,9 @@ _IRQ_CENTRAL_DISCONNECT --- instance1 --- gap_connect _IRQ_PERIPHERAL_CONNECT -_IRQ_GATTC_CHARACTERISTIC_RESULT UUID128('00000002-1111-2222-3333-444444444444') -_IRQ_GATTC_CHARACTERISTIC_RESULT UUID128('00000003-1111-2222-3333-444444444444') -_IRQ_GATTC_CHARACTERISTIC_RESULT UUID128('00000004-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000002-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000003-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000004-1111-2222-3333-444444444444') _IRQ_GATTC_CHARACTERISTIC_DONE gattc_write gattc_write diff --git a/tests/multi_bluetooth/ble_gattc_discover_services.py.exp b/tests/multi_bluetooth/ble_gattc_discover_services.py.exp index 06c36b2493..7f4d8da81a 100644 --- a/tests/multi_bluetooth/ble_gattc_discover_services.py.exp +++ b/tests/multi_bluetooth/ble_gattc_discover_services.py.exp @@ -5,7 +5,7 @@ _IRQ_CENTRAL_DISCONNECT --- instance1 --- gap_connect _IRQ_PERIPHERAL_CONNECT -_IRQ_GATTC_SERVICE_RESULT UUID16(0x180d) -_IRQ_GATTC_SERVICE_RESULT UUID128('a5a5a5a5-ffff-9999-1111-5a5a5a5a5a5a') +_IRQ_GATTC_SERVICE_RESULT UUID(0x180d) +_IRQ_GATTC_SERVICE_RESULT UUID('a5a5a5a5-ffff-9999-1111-5a5a5a5a5a5a') gap_disconnect: True _IRQ_PERIPHERAL_DISCONNECT From f271b96b5c8d6a4abedc81459a1c5710eb187abb Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 11 Sep 2020 13:24:12 +1000 Subject: [PATCH 055/337] docs/library/ubluetooth.rst: Add docs for MTU API. --- docs/library/ubluetooth.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/library/ubluetooth.rst b/docs/library/ubluetooth.rst index e4471f8054..03d03583a1 100644 --- a/docs/library/ubluetooth.rst +++ b/docs/library/ubluetooth.rst @@ -72,6 +72,13 @@ 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 + 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 + 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. + Event Handling -------------- @@ -161,6 +168,9 @@ Event Handling # A client has acknowledged the indication. # 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). + conn_handle, mtu = data The event codes are:: @@ -185,6 +195,7 @@ The event codes are:: _IRQ_GATTC_NOTIFY = const(18) _IRQ_GATTC_INDICATE = const(19) _IRQ_GATTS_INDICATE_DONE = const(20) + _IRQ_MTU_EXCHANGED = const(21) 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 @@ -459,6 +470,18 @@ device name from the device information service). If a response is received from the remote server the ``_IRQ_GATTC_WRITE_DONE`` event will be raised. +.. method:: BLE.gattc_exchange_mtu(conn_handle, /) + + Initiate MTU exchange with a connected server, using the preferred MTU + set using ``BLE.config(mtu=value)``. + + The ``_IRQ_MTU_EXCHANGED`` event will be raised when MTU exchange + completes. + + **Note:** MTU exchange is typically initiated by the central. When using + the BlueKitchen stack in the central role, it does not support a remote + peripheral initiating the MTU exchange. NimBLE works for both roles. + class UUID ---------- From 857e2c8fd54bc1d5ec3d3c0a38e4ac989e91413a Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 11 Sep 2020 18:12:36 +1000 Subject: [PATCH 056/337] extmod/modbluetooth: Implement MTU. --- extmod/btstack/modbluetooth_btstack.c | 36 ++++++++++++++++++++-- extmod/modbluetooth.c | 28 +++++++++++++++++ extmod/modbluetooth.h | 12 ++++++++ extmod/nimble/modbluetooth_nimble.c | 44 +++++++++++++++++++++++++-- 4 files changed, 116 insertions(+), 4 deletions(-) diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index cde802a61f..ae96035868 100644 --- a/extmod/btstack/modbluetooth_btstack.c +++ b/extmod/btstack/modbluetooth_btstack.c @@ -264,6 +264,11 @@ STATIC void btstack_packet_handler_att_server(uint8_t packet_type, uint16_t chan uint16_t value_handle = att_event_handle_value_indication_complete_get_attribute_handle(packet); uint8_t status = att_event_handle_value_indication_complete_get_status(packet); mp_bluetooth_gatts_on_indicate_complete(conn_handle, value_handle, status); + } else if (event_type == ATT_EVENT_MTU_EXCHANGE_COMPLETE) { + // This is triggered in peripheral mode, when exchange initiated by us or remote. + uint16_t conn_handle = att_event_mtu_exchange_complete_get_handle(packet); + uint16_t mtu = att_event_mtu_exchange_complete_get_MTU(packet); + mp_bluetooth_gatts_on_mtu_exchanged(conn_handle, mtu); } else if (event_type == HCI_EVENT_LE_META || event_type == HCI_EVENT_DISCONNECTION_COMPLETE) { // Ignore, duplicated by att_server.c. } else { @@ -339,8 +344,6 @@ 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 == GATT_EVENT_MTU) { - DEBUG_printf(" --> hci MTU\n"); } 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); @@ -621,6 +624,9 @@ int mp_bluetooth_init(void) { #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE gatt_client_init(); + + // We always require explicitly exchanging MTU with ble.gattc_exchange_mtu(). + gatt_client_mtu_enable_auto_negotiation(false); #endif // Register for HCI events. @@ -1042,6 +1048,24 @@ int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append return mp_bluetooth_gatts_db_resize(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, value_handle, len, append); } +int mp_bluetooth_get_preferred_mtu(void) { + if (!mp_bluetooth_is_active()) { + mp_raise_OSError(ERRNO_BLUETOOTH_NOT_ACTIVE); + } + return l2cap_max_le_mtu(); +} + +int mp_bluetooth_set_preferred_mtu(uint16_t mtu) { + if (!mp_bluetooth_is_active()) { + return ERRNO_BLUETOOTH_NOT_ACTIVE; + } + l2cap_set_max_le_mtu(mtu); + if (l2cap_max_le_mtu() != mtu) { + return MP_EINVAL; + } + return 0; +} + int mp_bluetooth_gap_disconnect(uint16_t conn_handle) { DEBUG_printf("mp_bluetooth_gap_disconnect\n"); gap_disconnect(conn_handle); @@ -1204,6 +1228,14 @@ int mp_bluetooth_gattc_write(uint16_t conn_handle, uint16_t value_handle, const return btstack_error_to_errno(err); } + +int mp_bluetooth_gattc_exchange_mtu(uint16_t conn_handle) { + DEBUG_printf("mp_bluetooth_exchange_mtu: conn_handle=%d mtu=%d\n", conn_handle, l2cap_max_le_mtu()); + + gatt_client_send_mtu_negotiation(&btstack_packet_handler_att_server, conn_handle); + + return 0; +} #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE #endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index f77c7eb63c..daf9cd0d19 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -316,6 +316,8 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map } case MP_QSTR_rxbuf: return mp_obj_new_int(self->ringbuf.size); + case MP_QSTR_mtu: + return mp_obj_new_int(mp_bluetooth_get_preferred_mtu()); default: mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); } @@ -368,6 +370,11 @@ 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; } + case MP_QSTR_mtu: { + mp_int_t mtu = mp_obj_get_int(e->value); + bluetooth_handle_errno(mp_bluetooth_set_preferred_mtu(mtu)); + break; + } case MP_QSTR_addr_mode: { mp_int_t addr_mode = mp_obj_get_int(e->value); mp_bluetooth_set_address_mode(addr_mode); @@ -770,6 +777,13 @@ STATIC mp_obj_t bluetooth_ble_gattc_write(size_t n_args, const mp_obj_t *args) { } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gattc_write_obj, 4, 5, bluetooth_ble_gattc_write); +STATIC mp_obj_t bluetooth_ble_gattc_exchange_mtu(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_gattc_exchange_mtu(conn_handle)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gattc_exchange_mtu_obj, bluetooth_ble_gattc_exchange_mtu); + #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE // ---------------------------------------------------------------------------- @@ -802,6 +816,7 @@ STATIC const mp_rom_map_elem_t bluetooth_ble_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_gattc_discover_descriptors), MP_ROM_PTR(&bluetooth_ble_gattc_discover_descriptors_obj) }, { MP_ROM_QSTR(MP_QSTR_gattc_read), MP_ROM_PTR(&bluetooth_ble_gattc_read_obj) }, { 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 }; STATIC MP_DEFINE_CONST_DICT(bluetooth_ble_locals_dict, bluetooth_ble_locals_dict_table); @@ -911,6 +926,9 @@ STATIC mp_obj_t bluetooth_ble_invoke_irq(mp_obj_t none_in) { } else if (event == MP_BLUETOOTH_IRQ_GATTS_INDICATE_DONE) { // conn_handle, value_handle, status ringbuf_extract(&o->ringbuf, data_tuple, 2, 1, NULL, 0, NULL, NULL); + } else if (event == MP_BLUETOOTH_IRQ_MTU_EXCHANGED) { + // conn_handle, mtu + ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, NULL, NULL); #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE } else if (event == MP_BLUETOOTH_IRQ_SCAN_RESULT) { // addr_type, addr, adv_type, rssi, adv_data @@ -1184,6 +1202,16 @@ bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_han } #endif +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); +} + 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 23ff97bc61..2df4c3c25f 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -100,6 +100,7 @@ #define MP_BLUETOOTH_IRQ_GATTC_NOTIFY (18) #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_ADDRESS_MODE_PUBLIC (0) #define MP_BLUETOOTH_ADDRESS_MODE_RANDOM (1) @@ -131,6 +132,7 @@ _IRQ_GATTC_WRITE_DONE = const(17) _IRQ_GATTC_NOTIFY = const(18) _IRQ_GATTC_INDICATE = const(19) _IRQ_GATTS_INDICATE_DONE = const(20) +_IRQ_MTU_EXCHANGED = const(21) */ // Common UUID type. @@ -212,6 +214,10 @@ int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append // Disconnect from a central or peripheral. int mp_bluetooth_gap_disconnect(uint16_t conn_handle); +// Set/get the MTU that we will respond to a MTU exchange with. +int mp_bluetooth_get_preferred_mtu(void); +int mp_bluetooth_set_preferred_mtu(uint16_t mtu); + #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); @@ -236,6 +242,9 @@ int mp_bluetooth_gattc_read(uint16_t conn_handle, uint16_t value_handle); // Write the value to the remote peripheral. int mp_bluetooth_gattc_write(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len, unsigned int mode); + +// Initiate MTU exchange for a specific connection using the preferred MTU. +int mp_bluetooth_gattc_exchange_mtu(uint16_t conn_handle); #endif ///////////////////////////////////////////////////////////////////////////// @@ -255,6 +264,9 @@ void mp_bluetooth_gatts_on_indicate_complete(uint16_t conn_handle, uint16_t valu 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); + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE // Notify modbluetooth that scan has finished, either timeout, manually, or by some other action (e.g. connecting). void mp_bluetooth_gap_on_scan_complete(void); diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 9ca90b6024..c0d6d64cfc 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -288,9 +288,17 @@ STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { // Map "done/ack" to 0, otherwise pass the status directly. mp_bluetooth_gatts_on_indicate_complete(event->notify_tx.conn_handle, event->notify_tx.attr_handle, event->notify_tx.status == BLE_HS_EDONE ? 0 : event->notify_tx.status); } + break; + } + + case BLE_GAP_EVENT_MTU: { + if (event->mtu.channel_id == BLE_L2CAP_CID_ATT) { + DEBUG_printf("gap_event_cb: mtu update: conn_handle=%d cid=%d mtu=%d\n", event->mtu.conn_handle, event->mtu.channel_id, event->mtu.value); + mp_bluetooth_gatts_on_mtu_exchanged(event->mtu.conn_handle, event->mtu.value); + } + break; } } - return 0; } @@ -637,7 +645,7 @@ int mp_bluetooth_gatts_register_service_begin(bool append) { return 0; } -int mp_bluetooth_gatts_register_service_end() { +int mp_bluetooth_gatts_register_service_end(void) { int ret = ble_gatts_start(); if (ret != 0) { return ble_hs_err_to_errno(ret); @@ -769,6 +777,23 @@ int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append return mp_bluetooth_gatts_db_resize(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, value_handle, len, append); } +int mp_bluetooth_get_preferred_mtu(void) { + if (!mp_bluetooth_is_active()) { + mp_raise_OSError(ERRNO_BLUETOOTH_NOT_ACTIVE); + } + return ble_att_preferred_mtu(); +} + +int mp_bluetooth_set_preferred_mtu(uint16_t mtu) { + if (!mp_bluetooth_is_active()) { + return ERRNO_BLUETOOTH_NOT_ACTIVE; + } + if (ble_att_set_preferred_mtu(mtu)) { + return MP_EINVAL; + } + return 0; +} + #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) { @@ -884,6 +909,14 @@ STATIC int peripheral_gap_event_cb(struct ble_gap_event *event, void *arg) { // TODO break; + case BLE_GAP_EVENT_MTU: { + if (event->mtu.channel_id == BLE_L2CAP_CID_ATT) { + DEBUG_printf("peripheral_gap_event_cb: mtu update: conn_handle=%d cid=%d mtu=%d\n", event->mtu.conn_handle, event->mtu.channel_id, event->mtu.value); + mp_bluetooth_gatts_on_mtu_exchanged(event->mtu.conn_handle, event->mtu.value); + } + break; + } + default: break; } @@ -1042,6 +1075,13 @@ int mp_bluetooth_gattc_write(uint16_t conn_handle, uint16_t value_handle, const return ble_hs_err_to_errno(err); } +int mp_bluetooth_gattc_exchange_mtu(uint16_t conn_handle) { + DEBUG_printf("mp_bluetooth_exchange_mtu: conn_handle=%d mtu=%d\n", conn_handle, ble_att_preferred_mtu()); + + // Using NULL callback (we'll get notified in gap_event_cb instead). + return ble_hs_err_to_errno(ble_gattc_exchange_mtu(conn_handle, NULL, NULL)); +} + #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE #endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE From 06dda48144be94ff6bef92b5bbf3fa87dea32cfa Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Mon, 14 Sep 2020 18:11:19 +1000 Subject: [PATCH 057/337] tests/run-multitests.py: Show test/truth diff. --- tests/run-multitests.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/run-multitests.py b/tests/run-multitests.py index 7ab4e85c57..ad032df38b 100755 --- a/tests/run-multitests.py +++ b/tests/run-multitests.py @@ -7,6 +7,7 @@ import sys, os, time, re, select import argparse import subprocess +import tempfile sys.path.append("../tools") import pyboard @@ -18,6 +19,9 @@ else: CPYTHON3 = os.getenv("MICROPY_CPYTHON3", "python3") MICROPYTHON = os.getenv("MICROPY_MICROPYTHON", "../ports/unix/micropython") +# For diff'ing test output +DIFF = os.getenv("MICROPY_DIFF", "diff -u") + PYTHON_TRUTH = CPYTHON3 INSTANCE_READ_TIMEOUT_S = 10 @@ -323,6 +327,18 @@ def run_test_on_instances(test_file, num_instances, instances): return error, skip, output_str +def print_diff(a, b): + a_fd, a_path = tempfile.mkstemp(text=True) + b_fd, b_path = tempfile.mkstemp(text=True) + os.write(a_fd, a.encode()) + os.write(b_fd, b.encode()) + os.close(a_fd) + os.close(b_fd) + subprocess.run(DIFF.split(" ") + [a_path, b_path]) + os.unlink(a_path) + os.unlink(b_path) + + def run_tests(test_files, instances_truth, instances_test): skipped_tests = [] passed_tests = [] @@ -372,6 +388,8 @@ def run_tests(test_files, instances_truth, instances_test): print(output_test, end="") print("### TRUTH ###") print(output_truth, end="") + print("### DIFF ###") + print_diff(output_test, output_truth) if cmd_args.show_output: print() From 3086d35e1637ba2bdc05ae676034d029ddbf302f Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Mon, 14 Sep 2020 18:11:46 +1000 Subject: [PATCH 058/337] tests/multi_bluetooth/ble_mtu.py: Add multitest for BLE MTU. --- tests/multi_bluetooth/ble_mtu.py | 208 +++++++++++++++++++++++++++ tests/multi_bluetooth/ble_mtu.py.exp | 143 ++++++++++++++++++ 2 files changed, 351 insertions(+) create mode 100644 tests/multi_bluetooth/ble_mtu.py create mode 100644 tests/multi_bluetooth/ble_mtu.py.exp diff --git a/tests/multi_bluetooth/ble_mtu.py b/tests/multi_bluetooth/ble_mtu.py new file mode 100644 index 0000000000..ef3199c5bb --- /dev/null +++ b/tests/multi_bluetooth/ble_mtu.py @@ -0,0 +1,208 @@ +# Test MTU exchange (initiated by both central and peripheral) and the effect on +# notify and write size. + +# Seven connections are made (four central->peripheral, three peripheral->central). +# +# Test | Requested | Preferred | Result | Notes +# 0 | 300 (C) | 256 (P) | 256 | +# 1 | 300 (C) | 200 (P) | 200 | +# 2 | 300 (C) | 400 (P) | 300 | +# 3 | 300 (C) | 50 (P) | 50 | Shorter than 64 so the notification is truncated. +# 4 | 290 (P) | 256 (C) | 256 | +# 5 | 290 (P) | 190 (C) | 190 | +# 6 | 290 (P) | 350 (C) | 290 | +# +# For each connection a notification is sent by the server (peripheral) and a characteristic +# is written by the client (central) to ensure that the expected size is transmitted. +# +# Note: This currently fails on btstack for two reasons: +# - btstack doesn't truncate writes to the MTU (it fails instead) +# - btstack (in central mode) doesn't handle the peripheral initiating the MTU exchange + +from micropython import const +import time, machine, bluetooth + +TIMEOUT_MS = 5000 + +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_GATTS_WRITE = const(3) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) +_IRQ_GATTC_CHARACTERISTIC_DONE = const(12) +_IRQ_GATTC_WRITE_DONE = const(17) +_IRQ_GATTC_NOTIFY = const(18) +_IRQ_MTU_EXCHANGED = const(21) + +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, +) +SERVICE = ( + SERVICE_UUID, + (CHAR,), +) +SERVICES = (SERVICE,) + +waiting_events = {} + + +def irq(event, data): + global 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") + 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] + elif event == _IRQ_GATTC_CHARACTERISTIC_DONE: + print("_IRQ_GATTC_CHARACTERISTIC_DONE") + elif event == _IRQ_GATTC_WRITE_DONE: + print("_IRQ_GATTC_WRITE_DONE") + elif event == _IRQ_GATTC_NOTIFY: + print("_IRQ_GATTC_NOTIFY", len(data[-1]), chr(data[-1][0])) + elif event == _IRQ_MTU_EXCHANGED: + print("_IRQ_MTU_EXCHANGED", data[-1]) + waiting_events[event] = data[-1] + + 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 True + machine.idle() + return False + + +# Acting in peripheral role. +def instance0(): + multitest.globals(BDADDR=ble.config("mac")) + ((char_handle,),) = ble.gatts_register_services(SERVICES) + ble.gatts_set_buffer(char_handle, 500, False) + print("gap_advertise") + ble.gap_advertise(20_000, b"\x02\x01\x06\x04\xffMPY") + multitest.next() + try: + for i in range(7): + waiting_events.clear() + + if i == 1: + ble.config(mtu=200) + elif i == 2: + ble.config(mtu=400) + elif i == 3: + ble.config(mtu=50) + elif i >= 4: + ble.config(mtu=290) + else: + # This is the NimBLE default. + 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] + + 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] + + print("gatts_notify") + ble.gatts_notify(conn_handle, char_handle, str(i) * 64) + + if not wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS): + return + + 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 + + print("gap_advertise") + ble.gap_advertise(20_000, b"\x02\x01\x06\x04\xffMPY") + + finally: + ble.active(0) + + +# Acting in central role. +def instance1(): + multitest.next() + try: + for i in range(7): + waiting_events.clear() + + if i < 4: + ble.config(mtu=300) + elif i == 5: + ble.config(mtu=190) + elif i == 6: + ble.config(mtu=350) + else: + ble.config(mtu=256) + + # 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_events[_IRQ_PERIPHERAL_CONNECT] + + 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] + + if not wait_for_event(_IRQ_GATTC_NOTIFY, TIMEOUT_MS): + return + + 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] + + # 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 + + # Disconnect from peripheral. + print("gap_disconnect:", ble.gap_disconnect(conn_handle)) + if not wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS): + return + finally: + ble.active(0) + + +ble = bluetooth.BLE() +ble.active(1) +ble.irq(irq) diff --git a/tests/multi_bluetooth/ble_mtu.py.exp b/tests/multi_bluetooth/ble_mtu.py.exp new file mode 100644 index 0000000000..1039a5da13 --- /dev/null +++ b/tests/multi_bluetooth/ble_mtu.py.exp @@ -0,0 +1,143 @@ +--- instance0 --- +gap_advertise +_IRQ_CENTRAL_CONNECT +_IRQ_MTU_EXCHANGED 256 +gatts_notify +_IRQ_GATTS_WRITE +gatts_read +characteristic len: 253 a +_IRQ_CENTRAL_DISCONNECT +gap_advertise +_IRQ_CENTRAL_CONNECT +_IRQ_MTU_EXCHANGED 200 +gatts_notify +_IRQ_GATTS_WRITE +gatts_read +characteristic len: 197 b +_IRQ_CENTRAL_DISCONNECT +gap_advertise +_IRQ_CENTRAL_CONNECT +_IRQ_MTU_EXCHANGED 300 +gatts_notify +_IRQ_GATTS_WRITE +gatts_read +characteristic len: 297 c +_IRQ_CENTRAL_DISCONNECT +gap_advertise +_IRQ_CENTRAL_CONNECT +_IRQ_MTU_EXCHANGED 50 +gatts_notify +_IRQ_GATTS_WRITE +gatts_read +characteristic len: 47 d +_IRQ_CENTRAL_DISCONNECT +gap_advertise +_IRQ_CENTRAL_CONNECT +gattc_exchange_mtu +_IRQ_MTU_EXCHANGED 256 +gatts_notify +_IRQ_GATTS_WRITE +gatts_read +characteristic len: 253 e +_IRQ_CENTRAL_DISCONNECT +gap_advertise +_IRQ_CENTRAL_CONNECT +gattc_exchange_mtu +_IRQ_MTU_EXCHANGED 190 +gatts_notify +_IRQ_GATTS_WRITE +gatts_read +characteristic len: 187 f +_IRQ_CENTRAL_DISCONNECT +gap_advertise +_IRQ_CENTRAL_CONNECT +gattc_exchange_mtu +_IRQ_MTU_EXCHANGED 290 +gatts_notify +_IRQ_GATTS_WRITE +gatts_read +characteristic len: 287 g +_IRQ_CENTRAL_DISCONNECT +gap_advertise +--- instance1 --- +gap_connect +_IRQ_PERIPHERAL_CONNECT +gattc_exchange_mtu +_IRQ_MTU_EXCHANGED 256 +_IRQ_GATTC_NOTIFY 64 0 +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_write +_IRQ_GATTC_WRITE_DONE +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT +gap_connect +_IRQ_PERIPHERAL_CONNECT +gattc_exchange_mtu +_IRQ_MTU_EXCHANGED 200 +_IRQ_GATTC_NOTIFY 64 1 +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_write +_IRQ_GATTC_WRITE_DONE +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT +gap_connect +_IRQ_PERIPHERAL_CONNECT +gattc_exchange_mtu +_IRQ_MTU_EXCHANGED 300 +_IRQ_GATTC_NOTIFY 64 2 +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_write +_IRQ_GATTC_WRITE_DONE +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT +gap_connect +_IRQ_PERIPHERAL_CONNECT +gattc_exchange_mtu +_IRQ_MTU_EXCHANGED 50 +_IRQ_GATTC_NOTIFY 47 3 +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_write +_IRQ_GATTC_WRITE_DONE +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT +gap_connect +_IRQ_PERIPHERAL_CONNECT +_IRQ_MTU_EXCHANGED 256 +_IRQ_GATTC_NOTIFY 64 4 +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_write +_IRQ_GATTC_WRITE_DONE +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT +gap_connect +_IRQ_PERIPHERAL_CONNECT +_IRQ_MTU_EXCHANGED 190 +_IRQ_GATTC_NOTIFY 64 5 +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_write +_IRQ_GATTC_WRITE_DONE +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT +gap_connect +_IRQ_PERIPHERAL_CONNECT +_IRQ_MTU_EXCHANGED 290 +_IRQ_GATTC_NOTIFY 64 6 +gattc_discover_characteristics +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gattc_write +_IRQ_GATTC_WRITE_DONE +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT From 5c503de52166f6ebd4ef59b58b130a730782823d Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 16 Sep 2020 16:34:09 +1000 Subject: [PATCH 059/337] travis: Install setuptools for black code formatting. Signed-off-by: Damien George --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index ebf9b55899..45b67dc9fe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,7 @@ jobs: install: - sudo apt-get install uncrustify python3-pip - uncrustify --version + - pip3 install --user setuptools - pip3 install --user black - black --version script: From 52d6eeb409e0d4ff3a1fd6811f75b4953912eb38 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Thu, 10 Sep 2020 11:13:13 +1000 Subject: [PATCH 060/337] esp32/boards/sdkconfig.base: Set default IDF log level to ERROR. This commit changes the default logging level on all esp32 boards to ERROR. The esp32 port is now stable enough that it makes sense to remove the info logs to make the output cleaner, and to match other ports. More verbose logging can always be reenabled via esp.osdebug(). This also fixes issue #6354, error messages from NimBLE: the problem is that ble.active(True) will cause the IDF's NimBLE port to reset the "NimBLE" tag back to the default level (which was INFO prior to this commit). Even if the user had previously called esp.osdebug(None), because the IDF is setting the "NimBLE" tag back to the default (INFO), the messages will continue to be shown. The one quirk is that if the user does want to see the additional logging, then they must call esp.osdebug(0, 3) after ble.active(True) to undo the IDF setting the level back to the default (now ERROR). This means that it's impossible (via Python/esp.osdebug) to see stack-startup logging, you'd have to recompile with the default level changed back to INFO. --- ports/esp32/boards/sdkconfig.base | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/esp32/boards/sdkconfig.base b/ports/esp32/boards/sdkconfig.base index f44ec4e173..67e2424a12 100644 --- a/ports/esp32/boards/sdkconfig.base +++ b/ports/esp32/boards/sdkconfig.base @@ -11,6 +11,11 @@ CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR=y # Bootloader config CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y +# Change default log level to "ERROR" (instead of "INFO") +CONFIG_LOG_DEFAULT_LEVEL_INFO=n +CONFIG_LOG_DEFAULT_LEVEL_ERROR=y +CONFIG_LOG_DEFAULT_LEVEL=1 + # ESP32-specific CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=n From ecb36d243915958e118a2c1d6b4bef8b490f0bc0 Mon Sep 17 00:00:00 2001 From: Mirko Vogt Date: Wed, 16 Sep 2020 23:57:40 +0000 Subject: [PATCH 061/337] esp32/modnetwork: Re-enable PPP support for IDF-SDK >=v4. PPP support was disabled in 96008ff59a8af9883af17d01b951029d9d02eec9 - marked as "unsupported" due to an early IDF v4 release. With the currently supported IDF v4.x version - 4c81978a - it appears to be working just fine. --- ports/esp32/modnetwork.c | 2 +- ports/esp32/network_ppp.c | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/ports/esp32/modnetwork.c b/ports/esp32/modnetwork.c index 325b27f74b..029a8d1431 100644 --- a/ports/esp32/modnetwork.c +++ b/ports/esp32/modnetwork.c @@ -755,8 +755,8 @@ STATIC const mp_rom_map_elem_t mp_module_network_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_WLAN), MP_ROM_PTR(&get_wlan_obj) }, #if !MICROPY_ESP_IDF_4 { MP_ROM_QSTR(MP_QSTR_LAN), MP_ROM_PTR(&get_lan_obj) }, - { MP_ROM_QSTR(MP_QSTR_PPP), MP_ROM_PTR(&ppp_make_new_obj) }, #endif + { MP_ROM_QSTR(MP_QSTR_PPP), MP_ROM_PTR(&ppp_make_new_obj) }, { MP_ROM_QSTR(MP_QSTR_phy_mode), MP_ROM_PTR(&esp_phy_mode_obj) }, #if MODNETWORK_INCLUDE_CONSTANTS diff --git a/ports/esp32/network_ppp.c b/ports/esp32/network_ppp.c index c3046c9fc7..df57b81725 100644 --- a/ports/esp32/network_ppp.c +++ b/ports/esp32/network_ppp.c @@ -26,7 +26,6 @@ * THE SOFTWARE. */ -#if !MICROPY_ESP_IDF_4 #include "py/runtime.h" #include "py/mphal.h" #include "py/objtype.h" @@ -284,5 +283,3 @@ const mp_obj_type_t ppp_if_type = { .name = MP_QSTR_PPP, .locals_dict = (mp_obj_dict_t *)&ppp_if_locals_dict, }; - -#endif // !MICROPY_ESP_IDF_4 From b28758054b586fe41f56e462856d89f28c5e3f6a Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 11 Sep 2020 16:47:29 +1000 Subject: [PATCH 062/337] esp8266: Remove release-specific manifest, disable osdebug by default. This commit removes release-specific builds for the esp8266 and makes the normal build of the GENERIC board more like the release build. This makes esp8266 like all the other ports, for which there is no difference between a daily build and a release build, making things less confusing. Release builds were previously defined by UART_OS=-1 (disable OS messages) and using manifest_release.py to include more frozen modules. The changes in this commit are: - Remove manifest_release.py. - Add existing modules from manifest_release.py (except example code) to the GENERIC board's manifest.py file. - Change UART_OS default to -1 to disable OS messages by default. Signed-off-by: Damien George --- ports/esp8266/Makefile | 2 +- ports/esp8266/boards/GENERIC/manifest.py | 19 +++++++++++++++++++ ports/esp8266/boards/manifest_release.py | 23 ----------------------- 3 files changed, 20 insertions(+), 24 deletions(-) delete mode 100644 ports/esp8266/boards/manifest_release.py diff --git a/ports/esp8266/Makefile b/ports/esp8266/Makefile index 38b91a545d..02ea05f76e 100644 --- a/ports/esp8266/Makefile +++ b/ports/esp8266/Makefile @@ -50,7 +50,7 @@ INC += -I$(ESP_SDK)/include # UART for "os" messages. 0 is normal UART as used by MicroPython REPL, # 1 is debug UART (tx only), -1 to disable. -UART_OS = 0 +UART_OS = -1 CFLAGS_XTENSA = -fsingle-precision-constant -Wdouble-promotion \ -D__ets__ -DICACHE_FLASH \ diff --git a/ports/esp8266/boards/GENERIC/manifest.py b/ports/esp8266/boards/GENERIC/manifest.py index 4e65b256f9..46f3b837be 100644 --- a/ports/esp8266/boards/GENERIC/manifest.py +++ b/ports/esp8266/boards/GENERIC/manifest.py @@ -1,2 +1,21 @@ +# base modules include("$(PORT_DIR)/boards/manifest.py") + +# uasyncio include("$(MPY_DIR)/extmod/uasyncio/manifest.py") + +# drivers +freeze("$(MPY_DIR)/drivers/display", "ssd1306.py") + +# Libraries from micropython-lib, include only if the library directory exists +if os.path.isdir(convert_path("$(MPY_LIB_DIR)")): + # file utilities + freeze("$(MPY_LIB_DIR)/upysh", "upysh.py") + + # requests + freeze("$(MPY_LIB_DIR)/urequests", "urequests.py") + freeze("$(MPY_LIB_DIR)/urllib.urequest", "urllib/urequest.py") + + # umqtt + freeze("$(MPY_LIB_DIR)/umqtt.simple", "umqtt/simple.py") + freeze("$(MPY_LIB_DIR)/umqtt.robust", "umqtt/robust.py") diff --git a/ports/esp8266/boards/manifest_release.py b/ports/esp8266/boards/manifest_release.py deleted file mode 100644 index 5a3194ae9b..0000000000 --- a/ports/esp8266/boards/manifest_release.py +++ /dev/null @@ -1,23 +0,0 @@ -include("manifest.py") - -# drivers -freeze("$(MPY_DIR)/drivers/display", "ssd1306.py") - -# file utilities -freeze("$(MPY_LIB_DIR)/upysh", "upysh.py") - -# requests -freeze("$(MPY_LIB_DIR)/urequests", "urequests.py") -freeze("$(MPY_LIB_DIR)/urllib.urequest", "urllib/urequest.py") - -# umqtt with examples -freeze("$(MPY_LIB_DIR)/umqtt.simple", "umqtt/simple.py") -freeze("$(MPY_LIB_DIR)/umqtt.robust", "umqtt/robust.py") -freeze("$(MPY_LIB_DIR)/umqtt.simple", "example_pub_button.py") -freeze("$(MPY_LIB_DIR)/umqtt.simple", "example_sub_led.py") - -# HTTP examples -freeze("$(MPY_DIR)/examples/network", "http_client.py") -freeze("$(MPY_DIR)/examples/network", "http_client_ssl.py") -freeze("$(MPY_DIR)/examples/network", "http_server.py") -freeze("$(MPY_DIR)/examples/network", "http_server_ssl.py") From bd7af6151d605d3fc8f70cb9ddf45b2fd7881f08 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 14 Sep 2020 00:07:12 +1000 Subject: [PATCH 063/337] ports: Add utime.gmtime() function. To portably get the Epoch. This is simply aliased to localtime() on ports that are not timezone aware. Signed-off-by: Damien George --- docs/library/utime.rst | 14 ++++++++++---- ports/cc3200/mods/modutime.c | 1 + ports/esp32/modutime.c | 1 + ports/esp8266/modules/ntptime.py | 5 ++--- ports/esp8266/modutime.c | 1 + ports/stm32/modutime.c | 1 + ports/unix/modtime.c | 14 ++++++++++++-- 7 files changed, 28 insertions(+), 9 deletions(-) diff --git a/docs/library/utime.rst b/docs/library/utime.rst index 7fe83f5abe..8c222ede5f 100644 --- a/docs/library/utime.rst +++ b/docs/library/utime.rst @@ -36,11 +36,17 @@ behave not as expected. Functions --------- -.. function:: localtime([secs]) +.. function:: gmtime([secs]) + localtime([secs]) - Convert a time expressed in seconds since the Epoch (see above) into an 8-tuple which - contains: (year, month, mday, hour, minute, second, weekday, yearday) - If secs is not provided or None, then the current time from the RTC is used. + Convert the time *secs* expressed in seconds since the Epoch (see above) into an + 8-tuple which contains: ``(year, month, mday, hour, minute, second, weekday, yearday)`` + If *secs* is not provided or None, then the current time from the RTC is used. + + The `gmtime()` function returns a date-time tuple in UTC, and `localtime()` returns a + date-time tuple in local time. + + The format of the entries in the 8-tuple are: * year includes the century (for example 2014). * month is 1-12 diff --git a/ports/cc3200/mods/modutime.c b/ports/cc3200/mods/modutime.c index a729d62f9c..e77065ef45 100644 --- a/ports/cc3200/mods/modutime.c +++ b/ports/cc3200/mods/modutime.c @@ -133,6 +133,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(time_sleep_obj, time_sleep); STATIC const mp_rom_map_elem_t time_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_utime) }, + { MP_ROM_QSTR(MP_QSTR_gmtime), MP_ROM_PTR(&time_localtime_obj) }, { MP_ROM_QSTR(MP_QSTR_localtime), MP_ROM_PTR(&time_localtime_obj) }, { MP_ROM_QSTR(MP_QSTR_mktime), MP_ROM_PTR(&time_mktime_obj) }, { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&time_time_obj) }, diff --git a/ports/esp32/modutime.c b/ports/esp32/modutime.c index 0325bd4693..1f93ce6c30 100644 --- a/ports/esp32/modutime.c +++ b/ports/esp32/modutime.c @@ -85,6 +85,7 @@ MP_DEFINE_CONST_FUN_OBJ_0(time_time_obj, time_time); STATIC const mp_rom_map_elem_t time_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_utime) }, + { MP_ROM_QSTR(MP_QSTR_gmtime), MP_ROM_PTR(&time_localtime_obj) }, { MP_ROM_QSTR(MP_QSTR_localtime), MP_ROM_PTR(&time_localtime_obj) }, { MP_ROM_QSTR(MP_QSTR_mktime), MP_ROM_PTR(&time_mktime_obj) }, { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&time_time_obj) }, diff --git a/ports/esp8266/modules/ntptime.py b/ports/esp8266/modules/ntptime.py index 92ae6ab077..dd07e46f1d 100644 --- a/ports/esp8266/modules/ntptime.py +++ b/ports/esp8266/modules/ntptime.py @@ -29,12 +29,11 @@ def time(): return val - NTP_DELTA -# There's currently no timezone support in MicroPython, so -# utime.localtime() will return UTC time (as if it was .gmtime()) +# There's currently no timezone support in MicroPython, and the RTC is set in UTC time. def settime(): t = time() import machine import utime - tm = utime.localtime(t) + tm = utime.gmtime(t) machine.RTC().datetime((tm[0], tm[1], tm[2], tm[6] + 1, tm[3], tm[4], tm[5], 0)) diff --git a/ports/esp8266/modutime.c b/ports/esp8266/modutime.c index 9e924121bb..a951f8f8d2 100644 --- a/ports/esp8266/modutime.c +++ b/ports/esp8266/modutime.c @@ -108,6 +108,7 @@ MP_DEFINE_CONST_FUN_OBJ_0(time_time_obj, time_time); STATIC const mp_rom_map_elem_t time_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_utime) }, + { MP_ROM_QSTR(MP_QSTR_gmtime), MP_ROM_PTR(&time_localtime_obj) }, { MP_ROM_QSTR(MP_QSTR_localtime), MP_ROM_PTR(&time_localtime_obj) }, { MP_ROM_QSTR(MP_QSTR_mktime), MP_ROM_PTR(&time_mktime_obj) }, { MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&mp_utime_sleep_obj) }, diff --git a/ports/stm32/modutime.c b/ports/stm32/modutime.c index 77ec7468c9..9641ddb473 100644 --- a/ports/stm32/modutime.c +++ b/ports/stm32/modutime.c @@ -132,6 +132,7 @@ MP_DEFINE_CONST_FUN_OBJ_0(time_time_obj, time_time); STATIC const mp_rom_map_elem_t time_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_utime) }, + { MP_ROM_QSTR(MP_QSTR_gmtime), MP_ROM_PTR(&time_localtime_obj) }, { MP_ROM_QSTR(MP_QSTR_localtime), MP_ROM_PTR(&time_localtime_obj) }, { MP_ROM_QSTR(MP_QSTR_mktime), MP_ROM_PTR(&time_mktime_obj) }, { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&time_time_obj) }, diff --git a/ports/unix/modtime.c b/ports/unix/modtime.c index 84c83da268..5343e479c7 100644 --- a/ports/unix/modtime.c +++ b/ports/unix/modtime.c @@ -132,7 +132,7 @@ STATIC mp_obj_t mod_time_sleep(mp_obj_t arg) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_time_sleep_obj, mod_time_sleep); -STATIC mp_obj_t mod_time_localtime(size_t n_args, const mp_obj_t *args) { +STATIC mp_obj_t mod_time_gm_local_time(size_t n_args, const mp_obj_t *args, struct tm *(*time_func)(const time_t *timep)) { time_t t; if (n_args == 0) { t = time(NULL); @@ -144,7 +144,7 @@ STATIC mp_obj_t mod_time_localtime(size_t n_args, const mp_obj_t *args) { t = mp_obj_get_int(args[0]); #endif } - struct tm *tm = localtime(&t); + struct tm *tm = time_func(&t); mp_obj_t ret = mp_obj_new_tuple(9, NULL); @@ -165,6 +165,15 @@ STATIC mp_obj_t mod_time_localtime(size_t n_args, const mp_obj_t *args) { return ret; } + +STATIC mp_obj_t mod_time_gmtime(size_t n_args, const mp_obj_t *args) { + return mod_time_gm_local_time(n_args, args, gmtime); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_time_gmtime_obj, 0, 1, mod_time_gmtime); + +STATIC mp_obj_t mod_time_localtime(size_t n_args, const mp_obj_t *args) { + return mod_time_gm_local_time(n_args, args, localtime); +} STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_time_localtime_obj, 0, 1, mod_time_localtime); STATIC mp_obj_t mod_time_mktime(mp_obj_t tuple) { @@ -210,6 +219,7 @@ STATIC const mp_rom_map_elem_t mp_module_time_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_ticks_cpu), MP_ROM_PTR(&mp_utime_ticks_cpu_obj) }, { MP_ROM_QSTR(MP_QSTR_ticks_add), MP_ROM_PTR(&mp_utime_ticks_add_obj) }, { MP_ROM_QSTR(MP_QSTR_ticks_diff), MP_ROM_PTR(&mp_utime_ticks_diff_obj) }, + { MP_ROM_QSTR(MP_QSTR_gmtime), MP_ROM_PTR(&mod_time_gmtime_obj) }, { MP_ROM_QSTR(MP_QSTR_localtime), MP_ROM_PTR(&mod_time_localtime_obj) }, { MP_ROM_QSTR(MP_QSTR_mktime), MP_ROM_PTR(&mod_time_mktime_obj) }, }; From 8f20cdc353a9d1c1e5d43484b58e1eaf81ec52e0 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 14 Sep 2020 12:15:03 +1000 Subject: [PATCH 064/337] all: Rename absolute time-based functions to include "epoch". For time-based functions that work with absolute time there is the need for an Epoch, to set the zero-point at which the absolute time starts counting. Such functions include time.time() and filesystem stat return values. And different ports may use a different Epoch. To make it clearer what functions use the Epoch (whatever it may be), and make the ports more consistent with their use of the Epoch, this commit renames all Epoch related functions to include the word "epoch" in their name (and remove references to "2000"). Along with this rename, the following things have changed: - mp_hal_time_ns() is now specified to return the number of nanoseconds since the Epoch, rather than since 1970 (but since this is an internal function it doesn't change anything for the user). - littlefs timestamps on the esp8266 have been fixed (they were previously off by 30 years in nanoseconds). Otherwise, there is no functional change made by this commit. Signed-off-by: Damien George --- extmod/vfs_fat.c | 5 +---- extmod/vfs_lfs.c | 4 +++- extmod/vfs_lfsx.c | 6 ++---- lib/timeutils/timeutils.h | 43 +++++++++++++++++++++++++++---------- ports/esp32/fatfs_port.c | 2 +- ports/esp32/machine_rtc.c | 4 ++-- ports/esp32/modutime.c | 2 +- ports/esp32/mphalport.c | 3 +-- ports/esp8266/esp_mphal.c | 2 +- ports/esp8266/fatfs_port.c | 4 ++-- ports/esp8266/machine_rtc.c | 18 ++++++++-------- ports/esp8266/modmachine.c | 2 +- ports/esp8266/modmachine.h | 4 ++-- ports/esp8266/modutime.c | 8 +++---- ports/stm32/modutime.c | 4 ++-- ports/stm32/rtc.c | 4 ++-- py/mphal.h | 2 +- 17 files changed, 67 insertions(+), 50 deletions(-) diff --git a/extmod/vfs_fat.c b/extmod/vfs_fat.c index ace5ba5b65..95b7ad9944 100644 --- a/extmod/vfs_fat.c +++ b/extmod/vfs_fat.c @@ -311,7 +311,7 @@ STATIC mp_obj_t fat_vfs_stat(mp_obj_t vfs_in, mp_obj_t path_in) { } else { mode |= MP_S_IFREG; } - mp_int_t seconds = timeutils_seconds_since_2000( + mp_int_t seconds = timeutils_seconds_since_epoch( 1980 + ((fno.fdate >> 9) & 0x7f), (fno.fdate >> 5) & 0x0f, fno.fdate & 0x1f, @@ -319,9 +319,6 @@ STATIC mp_obj_t fat_vfs_stat(mp_obj_t vfs_in, mp_obj_t path_in) { (fno.ftime >> 5) & 0x3f, 2 * (fno.ftime & 0x1f) ); - #if MICROPY_EPOCH_IS_1970 - seconds += TIMEUTILS_SECONDS_1970_TO_2000; - #endif t->items[0] = MP_OBJ_NEW_SMALL_INT(mode); // st_mode t->items[1] = MP_OBJ_NEW_SMALL_INT(0); // st_ino t->items[2] = MP_OBJ_NEW_SMALL_INT(0); // st_dev diff --git a/extmod/vfs_lfs.c b/extmod/vfs_lfs.c index a53f66f2d6..9cf3eb1108 100644 --- a/extmod/vfs_lfs.c +++ b/extmod/vfs_lfs.c @@ -26,6 +26,7 @@ #include "py/runtime.h" #include "py/mphal.h" +#include "lib/timeutils/timeutils.h" #include "extmod/vfs.h" #include "extmod/vfs_lfs.h" @@ -126,7 +127,8 @@ const char *mp_vfs_lfs2_make_path(mp_obj_vfs_lfs2_t *self, mp_obj_t path_in); mp_obj_t mp_vfs_lfs2_file_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_in); STATIC void lfs_get_mtime(uint8_t buf[8]) { - uint64_t ns = mp_hal_time_ns(); + // On-disk storage of timestamps uses 1970 as the Epoch, so convert from host's Epoch. + uint64_t ns = timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970(mp_hal_time_ns()); // Store "ns" to "buf" in little-endian format (essentially htole64). for (size_t i = 0; i < 8; ++i) { buf[i] = ns; diff --git a/extmod/vfs_lfsx.c b/extmod/vfs_lfsx.c index d00df53104..35d5f03c59 100644 --- a/extmod/vfs_lfsx.c +++ b/extmod/vfs_lfsx.c @@ -365,10 +365,8 @@ STATIC mp_obj_t MP_VFS_LFSx(stat)(mp_obj_t self_in, mp_obj_t path_in) { for (size_t i = sizeof(mtime_buf); i > 0; --i) { ns = ns << 8 | mtime_buf[i - 1]; } - mtime = timeutils_seconds_since_2000_from_nanoseconds_since_1970(ns); - #if MICROPY_EPOCH_IS_1970 - mtime += TIMEUTILS_SECONDS_1970_TO_2000; - #endif + // On-disk storage of timestamps uses 1970 as the Epoch, so convert to host's Epoch. + mtime = timeutils_seconds_since_epoch_from_nanoseconds_since_1970(ns); } #endif diff --git a/lib/timeutils/timeutils.h b/lib/timeutils/timeutils.h index 08b0dc2e85..14da831dc8 100644 --- a/lib/timeutils/timeutils.h +++ b/lib/timeutils/timeutils.h @@ -42,14 +42,6 @@ typedef struct _timeutils_struct_time_t { uint16_t tm_yday; // 1..366 } timeutils_struct_time_t; -static inline uint64_t timeutils_seconds_since_2000_to_nanoseconds_since_1970(mp_uint_t s) { - return ((uint64_t)s + TIMEUTILS_SECONDS_1970_TO_2000) * 1000000000ULL; -} - -static inline mp_uint_t timeutils_seconds_since_2000_from_nanoseconds_since_1970(uint64_t ns) { - return ns / 1000000000ULL - TIMEUTILS_SECONDS_1970_TO_2000; -} - bool timeutils_is_leap_year(mp_uint_t year); mp_uint_t timeutils_days_in_month(mp_uint_t year, mp_uint_t month); mp_uint_t timeutils_year_day(mp_uint_t year, mp_uint_t month, mp_uint_t date); @@ -63,10 +55,39 @@ mp_uint_t timeutils_seconds_since_2000(mp_uint_t year, mp_uint_t month, mp_uint_t timeutils_mktime(mp_uint_t year, mp_int_t month, mp_int_t mday, mp_int_t hours, mp_int_t minutes, mp_int_t seconds); -static inline uint64_t timeutils_nanoseconds_since_1970(mp_uint_t year, mp_uint_t month, +// Select the Epoch used by the port. +#if MICROPY_EPOCH_IS_1970 + +static inline uint64_t timeutils_seconds_since_epoch(mp_uint_t year, mp_uint_t month, mp_uint_t date, mp_uint_t hour, mp_uint_t minute, mp_uint_t second) { - return timeutils_seconds_since_2000_to_nanoseconds_since_1970( - timeutils_seconds_since_2000(year, month, date, hour, minute, second)); + return timeutils_seconds_since_2000(year, month, date, hour, minute, second) + TIMEUTILS_SECONDS_1970_TO_2000; } +static inline mp_uint_t timeutils_seconds_since_epoch_from_nanoseconds_since_1970(uint64_t ns) { + return ns / 1000000000ULL; +} + +static inline uint64_t timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970(uint64_t ns) { + return ns; +} + +#else // Epoch is 2000 + +#define timeutils_seconds_since_epoch_to_struct_time timeutils_seconds_since_2000_to_struct_time +#define timeutils_seconds_since_epoch timeutils_seconds_since_2000 + +static inline uint64_t timeutils_seconds_since_epoch_to_nanoseconds_since_1970(mp_uint_t s) { + return ((uint64_t)s + TIMEUTILS_SECONDS_1970_TO_2000) * 1000000000ULL; +} + +static inline mp_uint_t timeutils_seconds_since_epoch_from_nanoseconds_since_1970(uint64_t ns) { + return ns / 1000000000ULL - TIMEUTILS_SECONDS_1970_TO_2000; +} + +static inline int64_t timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970(int64_t ns) { + return ns + TIMEUTILS_SECONDS_1970_TO_2000 * 1000000000ULL; +} + +#endif + #endif // MICROPY_INCLUDED_LIB_TIMEUTILS_TIMEUTILS_H diff --git a/ports/esp32/fatfs_port.c b/ports/esp32/fatfs_port.c index 779ba1c88a..cfc52853d0 100644 --- a/ports/esp32/fatfs_port.c +++ b/ports/esp32/fatfs_port.c @@ -34,7 +34,7 @@ DWORD get_fattime(void) { struct timeval tv; gettimeofday(&tv, NULL); timeutils_struct_time_t tm; - timeutils_seconds_since_2000_to_struct_time(tv.tv_sec, &tm); + timeutils_seconds_since_epoch_to_struct_time(tv.tv_sec, &tm); return ((DWORD)(tm.tm_year - 1980) << 25) | ((DWORD)tm.tm_mon << 21) | ((DWORD)tm.tm_mday << 16) | ((DWORD)tm.tm_hour << 11) | ((DWORD)tm.tm_min << 5) | ((DWORD)tm.tm_sec >> 1); diff --git a/ports/esp32/machine_rtc.c b/ports/esp32/machine_rtc.c index c776eaa8d3..6902ce85fd 100644 --- a/ports/esp32/machine_rtc.c +++ b/ports/esp32/machine_rtc.c @@ -93,7 +93,7 @@ STATIC mp_obj_t machine_rtc_datetime_helper(mp_uint_t n_args, const mp_obj_t *ar gettimeofday(&tv, NULL); timeutils_struct_time_t tm; - timeutils_seconds_since_2000_to_struct_time(tv.tv_sec, &tm); + timeutils_seconds_since_epoch_to_struct_time(tv.tv_sec, &tm); mp_obj_t tuple[8] = { mp_obj_new_int(tm.tm_year), @@ -114,7 +114,7 @@ STATIC mp_obj_t machine_rtc_datetime_helper(mp_uint_t n_args, const mp_obj_t *ar mp_obj_get_array_fixed_n(args[1], 8, &items); struct timeval tv = {0}; - tv.tv_sec = timeutils_seconds_since_2000(mp_obj_get_int(items[0]), mp_obj_get_int(items[1]), mp_obj_get_int(items[2]), mp_obj_get_int(items[4]), mp_obj_get_int(items[5]), mp_obj_get_int(items[6])); + tv.tv_sec = timeutils_seconds_since_epoch(mp_obj_get_int(items[0]), mp_obj_get_int(items[1]), mp_obj_get_int(items[2]), mp_obj_get_int(items[4]), mp_obj_get_int(items[5]), mp_obj_get_int(items[6])); tv.tv_usec = mp_obj_get_int(items[7]); settimeofday(&tv, NULL); diff --git a/ports/esp32/modutime.c b/ports/esp32/modutime.c index 1f93ce6c30..ac3edb4e9b 100644 --- a/ports/esp32/modutime.c +++ b/ports/esp32/modutime.c @@ -44,7 +44,7 @@ STATIC mp_obj_t time_localtime(size_t n_args, const mp_obj_t *args) { } else { seconds = mp_obj_get_int(args[0]); } - timeutils_seconds_since_2000_to_struct_time(seconds, &tm); + timeutils_seconds_since_epoch_to_struct_time(seconds, &tm); mp_obj_t tuple[8] = { tuple[0] = mp_obj_new_int(tm.tm_year), tuple[1] = mp_obj_new_int(tm.tm_mon), diff --git a/ports/esp32/mphalport.c b/ports/esp32/mphalport.c index 3a46edf1f7..ad571bf961 100644 --- a/ports/esp32/mphalport.c +++ b/ports/esp32/mphalport.c @@ -200,8 +200,7 @@ void mp_hal_delay_us(uint32_t us) { uint64_t mp_hal_time_ns(void) { struct timeval tv; gettimeofday(&tv, NULL); - // gettimeofday returns seconds since 2000/1/1 - uint64_t ns = timeutils_seconds_since_2000_to_nanoseconds_since_1970(tv.tv_sec); + uint64_t ns = tv.tv_sec * 1000000000ULL; ns += (uint64_t)tv.tv_usec * 1000ULL; return ns; } diff --git a/ports/esp8266/esp_mphal.c b/ports/esp8266/esp_mphal.c index 9e1b72e438..54f9611e56 100644 --- a/ports/esp8266/esp_mphal.c +++ b/ports/esp8266/esp_mphal.c @@ -139,7 +139,7 @@ void MP_FASTCODE(mp_hal_delay_ms)(uint32_t delay) { } uint64_t mp_hal_time_ns(void) { - return pyb_rtc_get_us_since_2000() * 1000ULL; + return pyb_rtc_get_us_since_epoch() * 1000ULL; } void ets_event_poll(void) { diff --git a/ports/esp8266/fatfs_port.c b/ports/esp8266/fatfs_port.c index 8cef0acec3..bbd1051935 100644 --- a/ports/esp8266/fatfs_port.c +++ b/ports/esp8266/fatfs_port.c @@ -33,10 +33,10 @@ DWORD get_fattime(void) { // TODO: Optimize division (there's no HW division support on ESP8266, // so it's expensive). - uint32_t secs = (uint32_t)(pyb_rtc_get_us_since_2000() / 1000000); + uint32_t secs = (uint32_t)(pyb_rtc_get_us_since_epoch() / 1000000); timeutils_struct_time_t tm; - timeutils_seconds_since_2000_to_struct_time(secs, &tm); + timeutils_seconds_since_epoch_to_struct_time(secs, &tm); return ((DWORD)(tm.tm_year - 1980) << 25) | ((DWORD)tm.tm_mon << 21) | ((DWORD)tm.tm_mday << 16) | ((DWORD)tm.tm_hour << 11) | ((DWORD)tm.tm_min << 5) | ((DWORD)tm.tm_sec >> 1); diff --git a/ports/esp8266/machine_rtc.c b/ports/esp8266/machine_rtc.c index 1aa73f9b31..e7b750fd92 100644 --- a/ports/esp8266/machine_rtc.c +++ b/ports/esp8266/machine_rtc.c @@ -84,7 +84,7 @@ STATIC mp_obj_t pyb_rtc_make_new(const mp_obj_type_t *type, size_t n_args, size_ return (mp_obj_t)&pyb_rtc_obj; } -void pyb_rtc_set_us_since_2000(uint64_t nowus) { +void pyb_rtc_set_us_since_epoch(uint64_t nowus) { uint32_t cal = system_rtc_clock_cali_proc(); // Save RTC ticks for overflow detection. rtc_last_ticks = system_get_rtc_time(); @@ -96,7 +96,7 @@ void pyb_rtc_set_us_since_2000(uint64_t nowus) { system_rtc_mem_write(MEM_DELTA_ADDR, &delta, sizeof(delta)); }; -uint64_t pyb_rtc_get_us_since_2000() { +uint64_t pyb_rtc_get_us_since_epoch() { uint32_t cal; int64_t delta; uint32_t rtc_ticks; @@ -120,17 +120,17 @@ uint64_t pyb_rtc_get_us_since_2000() { void rtc_prepare_deepsleep(uint64_t sleep_us) { // RTC time will reset at wake up. Let's be preared for this. - int64_t delta = pyb_rtc_get_us_since_2000() + sleep_us; + int64_t delta = pyb_rtc_get_us_since_epoch() + sleep_us; system_rtc_mem_write(MEM_DELTA_ADDR, &delta, sizeof(delta)); } STATIC mp_obj_t pyb_rtc_datetime(size_t n_args, const mp_obj_t *args) { if (n_args == 1) { // Get time - uint64_t msecs = pyb_rtc_get_us_since_2000() / 1000; + uint64_t msecs = pyb_rtc_get_us_since_epoch() / 1000; timeutils_struct_time_t tm; - timeutils_seconds_since_2000_to_struct_time(msecs / 1000, &tm); + timeutils_seconds_since_epoch_to_struct_time(msecs / 1000, &tm); mp_obj_t tuple[8] = { mp_obj_new_int(tm.tm_year), @@ -149,8 +149,8 @@ STATIC mp_obj_t pyb_rtc_datetime(size_t n_args, const mp_obj_t *args) { mp_obj_t *items; mp_obj_get_array_fixed_n(args[1], 8, &items); - pyb_rtc_set_us_since_2000( - ((uint64_t)timeutils_seconds_since_2000( + pyb_rtc_set_us_since_epoch( + ((uint64_t)timeutils_seconds_since_epoch( mp_obj_get_int(items[0]), mp_obj_get_int(items[1]), mp_obj_get_int(items[2]), @@ -209,7 +209,7 @@ STATIC mp_obj_t pyb_rtc_alarm(mp_obj_t self_in, mp_obj_t alarm_id, mp_obj_t time } // set expiry time (in microseconds) - pyb_rtc_alarm0_expiry = pyb_rtc_get_us_since_2000() + (uint64_t)mp_obj_get_int(time_in) * 1000; + pyb_rtc_alarm0_expiry = pyb_rtc_get_us_since_epoch() + (uint64_t)mp_obj_get_int(time_in) * 1000; return mp_const_none; @@ -222,7 +222,7 @@ STATIC mp_obj_t pyb_rtc_alarm_left(size_t n_args, const mp_obj_t *args) { mp_raise_ValueError(MP_ERROR_TEXT("invalid alarm")); } - uint64_t now = pyb_rtc_get_us_since_2000(); + uint64_t now = pyb_rtc_get_us_since_epoch(); if (pyb_rtc_alarm0_expiry <= now) { return MP_OBJ_NEW_SMALL_INT(0); } else { diff --git a/ports/esp8266/modmachine.c b/ports/esp8266/modmachine.c index bc838b4206..cb28d5b144 100644 --- a/ports/esp8266/modmachine.c +++ b/ports/esp8266/modmachine.c @@ -132,7 +132,7 @@ STATIC mp_obj_t machine_deepsleep(size_t n_args, const mp_obj_t *args) { // see if RTC.ALARM0 should wake the device if (pyb_rtc_alarm0_wake & MACHINE_WAKE_DEEPSLEEP) { - uint64_t t = pyb_rtc_get_us_since_2000(); + uint64_t t = pyb_rtc_get_us_since_epoch(); if (pyb_rtc_alarm0_expiry <= t) { sleep_us = 1; // alarm already expired so wake immediately } else { diff --git a/ports/esp8266/modmachine.h b/ports/esp8266/modmachine.h index f5cfc0fa2c..be4debd335 100644 --- a/ports/esp8266/modmachine.h +++ b/ports/esp8266/modmachine.h @@ -32,8 +32,8 @@ void pin_set(uint pin, int value); extern uint32_t pyb_rtc_alarm0_wake; extern uint64_t pyb_rtc_alarm0_expiry; -void pyb_rtc_set_us_since_2000(uint64_t nowus); -uint64_t pyb_rtc_get_us_since_2000(); +void pyb_rtc_set_us_since_epoch(uint64_t nowus); +uint64_t pyb_rtc_get_us_since_epoch(); void rtc_prepare_deepsleep(uint64_t sleep_us); #endif // MICROPY_INCLUDED_ESP8266_MODMACHINE_H diff --git a/ports/esp8266/modutime.c b/ports/esp8266/modutime.c index a951f8f8d2..86534722c7 100644 --- a/ports/esp8266/modutime.c +++ b/ports/esp8266/modutime.c @@ -58,11 +58,11 @@ STATIC mp_obj_t time_localtime(size_t n_args, const mp_obj_t *args) { timeutils_struct_time_t tm; mp_int_t seconds; if (n_args == 0 || args[0] == mp_const_none) { - seconds = pyb_rtc_get_us_since_2000() / 1000 / 1000; + seconds = pyb_rtc_get_us_since_epoch() / 1000 / 1000; } else { seconds = mp_obj_get_int(args[0]); } - timeutils_seconds_since_2000_to_struct_time(seconds, &tm); + timeutils_seconds_since_epoch_to_struct_time(seconds, &tm); mp_obj_t tuple[8] = { tuple[0] = mp_obj_new_int(tm.tm_year), tuple[1] = mp_obj_new_int(tm.tm_mon), @@ -98,10 +98,10 @@ STATIC mp_obj_t time_mktime(mp_obj_t tuple) { MP_DEFINE_CONST_FUN_OBJ_1(time_mktime_obj, time_mktime); /// \function time() -/// Returns the number of seconds, as an integer, since 1/1/2000. +/// Returns the number of seconds, as an integer, since the Epoch. STATIC mp_obj_t time_time(void) { // get date and time - return mp_obj_new_int(pyb_rtc_get_us_since_2000() / 1000 / 1000); + return mp_obj_new_int(pyb_rtc_get_us_since_epoch() / 1000 / 1000); } MP_DEFINE_CONST_FUN_OBJ_0(time_time_obj, time_time); diff --git a/ports/stm32/modutime.c b/ports/stm32/modutime.c index 9641ddb473..2a37a130df 100644 --- a/ports/stm32/modutime.c +++ b/ports/stm32/modutime.c @@ -76,7 +76,7 @@ STATIC mp_obj_t time_localtime(size_t n_args, const mp_obj_t *args) { } else { mp_int_t seconds = mp_obj_get_int(args[0]); timeutils_struct_time_t tm; - timeutils_seconds_since_2000_to_struct_time(seconds, &tm); + timeutils_seconds_since_epoch_to_struct_time(seconds, &tm); mp_obj_t tuple[8] = { tuple[0] = mp_obj_new_int(tm.tm_year), tuple[1] = mp_obj_new_int(tm.tm_mon), @@ -125,7 +125,7 @@ STATIC mp_obj_t time_time(void) { RTC_TimeTypeDef time; HAL_RTC_GetTime(&RTCHandle, &time, RTC_FORMAT_BIN); HAL_RTC_GetDate(&RTCHandle, &date, RTC_FORMAT_BIN); - return mp_obj_new_int(timeutils_seconds_since_2000(2000 + date.Year, date.Month, date.Date, time.Hours, time.Minutes, time.Seconds)); + return mp_obj_new_int(timeutils_seconds_since_epoch(2000 + date.Year, date.Month, date.Date, time.Hours, time.Minutes, time.Seconds)); } MP_DEFINE_CONST_FUN_OBJ_0(time_time_obj, time_time); diff --git a/ports/stm32/rtc.c b/ports/stm32/rtc.c index 4f759c4bc7..18ecf77505 100644 --- a/ports/stm32/rtc.c +++ b/ports/stm32/rtc.c @@ -452,8 +452,8 @@ uint64_t mp_hal_time_ns(void) { RTC_DateTypeDef date; HAL_RTC_GetTime(&RTCHandle, &time, RTC_FORMAT_BIN); HAL_RTC_GetDate(&RTCHandle, &date, RTC_FORMAT_BIN); - ns = timeutils_nanoseconds_since_1970( - 2000 + date.Year, date.Month, date.Date, time.Hours, time.Minutes, time.Seconds); + ns = timeutils_seconds_since_epoch(2000 + date.Year, date.Month, date.Date, time.Hours, time.Minutes, time.Seconds); + ns *= 1000000000ULL; uint32_t usec = ((RTC_SYNCH_PREDIV - time.SubSeconds) * (1000000 / 64)) / ((RTC_SYNCH_PREDIV + 1) / 64); ns += usec * 1000; #endif diff --git a/py/mphal.h b/py/mphal.h index 6d11f6ddc0..0d4b1224e5 100644 --- a/py/mphal.h +++ b/py/mphal.h @@ -76,7 +76,7 @@ mp_uint_t mp_hal_ticks_cpu(void); #endif #ifndef mp_hal_time_ns -// Nanoseconds since 1970/1/1. +// Nanoseconds since the Epoch. uint64_t mp_hal_time_ns(void); #endif From 9d1983f078150b0f1da7bfb2e55c0ac823c328b6 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Wed, 16 Sep 2020 12:11:26 +1000 Subject: [PATCH 065/337] py/dynruntime.h: Add mp_import_* and mp_load/store_*. These functions already exist in the fun table, and this commit just adds convenience macros for them. --- py/dynruntime.h | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/py/dynruntime.h b/py/dynruntime.h index 696746f751..eb8301284e 100644 --- a/py/dynruntime.h +++ b/py/dynruntime.h @@ -163,9 +163,15 @@ static inline mp_obj_t mp_obj_len_dyn(mp_obj_t o) { /******************************************************************************/ // General runtime functions -#define mp_load_name(qst) (mp_fun_table.load_name(qst)) -#define mp_load_global(qst) (mp_fun_table.load_global(qst)) -#define mp_store_global(qst, obj) (mp_fun_table.store_global((qst), (obj))) +#define mp_load_name(qst) (mp_fun_table.load_name((qst))) +#define mp_load_global(qst) (mp_fun_table.load_global((qst))) +#define mp_load_attr(base, attr) (mp_fun_table.load_attr((base), (attr))) +#define mp_load_method(base, attr, dest) (mp_fun_table.load_method((base), (attr), (dest))) +#define mp_load_super_method(attr, dest) (mp_fun_table.load_super_method((attr), (dest))) +#define mp_store_name(qst, obj) (mp_fun_table.store_name((qst), (obj))) +#define mp_store_global(qst, obj) (mp_fun_table.store_global((qst), (obj))) +#define mp_store_attr(base, attr, val) (mp_fun_table.store_attr((base), (attr), (val))) + #define mp_unary_op(op, obj) (mp_fun_table.unary_op((op), (obj))) #define mp_binary_op(op, lhs, rhs) (mp_fun_table.binary_op((op), (lhs), (rhs))) @@ -193,6 +199,13 @@ static inline mp_obj_t mp_obj_len_dyn(mp_obj_t o) { #define MP_DYNRUNTIME_MAKE_FUNCTION(f) \ (mp_make_function_from_raw_code((rc.fun_data = (f), &rc), MP_OBJ_NULL, MP_OBJ_NULL)) +#define mp_import_name(name, fromlist, level) \ + (mp_fun_table.import_name((name), (fromlist), (level))) +#define mp_import_from(module, name) \ + (mp_fun_table.import_from((module), (name))) +#define mp_import_all(module) \ + (mp_fun_table.import_all((module)) + /******************************************************************************/ // Exceptions From 8af9796b1642189f590520062b1a00e965d5985e Mon Sep 17 00:00:00 2001 From: Tweako Date: Wed, 16 Sep 2020 10:29:33 +0200 Subject: [PATCH 066/337] stm32/led: Support PWM output without TIM3. For example, the STM32WB55 doesn't have TIM3 but can still drive LEDs using PWM on other timers. --- ports/stm32/led.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/stm32/led.c b/ports/stm32/led.c index 9231718846..adcb240cc0 100644 --- a/ports/stm32/led.c +++ b/ports/stm32/led.c @@ -141,9 +141,11 @@ STATIC void led_pwm_init(int led) { case 2: __TIM2_CLK_ENABLE(); break; + #if defined(TIM3) case 3: __TIM3_CLK_ENABLE(); break; + #endif default: assert(0); } From c410a86814abde8ce05aaf46383f38ef9f981a8c Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 17 Sep 2020 13:26:24 +1000 Subject: [PATCH 067/337] tests/basics: Enable == and != special-method tests now that they work. These work since 3aab54bf434e7f025a91ea05052f1bac439fad8c Signed-off-by: Damien George --- tests/basics/special_methods.py | 4 ++++ tests/basics/special_methods2.py | 9 --------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/tests/basics/special_methods.py b/tests/basics/special_methods.py index b56bc1c9c4..d8af8e0797 100644 --- a/tests/basics/special_methods.py +++ b/tests/basics/special_methods.py @@ -100,6 +100,10 @@ cud1 = Cud() cud2 = Cud() str(cud1) +cud1 == cud1 +cud1 == cud2 +cud1 != cud1 +cud1 != cud2 cud1 < cud2 cud1 <= cud2 cud1 == cud2 diff --git a/tests/basics/special_methods2.py b/tests/basics/special_methods2.py index 09e43fff29..31f330ab42 100644 --- a/tests/basics/special_methods2.py +++ b/tests/basics/special_methods2.py @@ -129,12 +129,3 @@ print(dir(cud1)) # test that dir() does not delegate to __dir__ for the type print('a' in dir(Cud)) - -# TODO: the following operations are not supported on every ports -# -# ne is not supported, !(eq) is called instead -#cud1 != cud2 -# -# in the following test, cpython still calls __eq__ -# cud3=cud1 -# cud3==cud1 From 42342fa3cb30e2eac56ceb1d21b4eb60a0f406f3 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 17 Sep 2020 13:37:31 +1000 Subject: [PATCH 068/337] tests/basics: Add test for MICROPY_PY_ALL_INPLACE_SPECIAL_METHODS ops. And enable this feature on unix, the coverage variant. The .exp test file is needed so the test can run on CPython versions prior to "@=" operator support. Signed-off-by: Damien George --- .../unix/variants/coverage/mpconfigvariant.h | 1 + tests/basics/class_inplace_op2.py | 73 +++++++++++++++++++ tests/basics/class_inplace_op2.py.exp | 12 +++ 3 files changed, 86 insertions(+) create mode 100644 tests/basics/class_inplace_op2.py create mode 100644 tests/basics/class_inplace_op2.py.exp diff --git a/ports/unix/variants/coverage/mpconfigvariant.h b/ports/unix/variants/coverage/mpconfigvariant.h index 802c2fe5f7..3de5294322 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.h +++ b/ports/unix/variants/coverage/mpconfigvariant.h @@ -39,6 +39,7 @@ #define MICROPY_WARNINGS_CATEGORY (1) #define MICROPY_MODULE_GETATTR (1) #define MICROPY_PY_DELATTR_SETATTR (1) +#define MICROPY_PY_ALL_INPLACE_SPECIAL_METHODS (1) #define MICROPY_PY_REVERSE_SPECIAL_METHODS (1) #define MICROPY_PY_BUILTINS_MEMORYVIEW_ITEMSIZE (1) #define MICROPY_PY_BUILTINS_NEXT2 (1) diff --git a/tests/basics/class_inplace_op2.py b/tests/basics/class_inplace_op2.py new file mode 100644 index 0000000000..0e3f2add41 --- /dev/null +++ b/tests/basics/class_inplace_op2.py @@ -0,0 +1,73 @@ +# Test inplace special methods enabled by MICROPY_PY_ALL_INPLACE_SPECIAL_METHODS + + +class A: + def __imul__(self, other): + print("__imul__") + return self + + def __imatmul__(self, other): + print("__imatmul__") + return self + + def __ifloordiv__(self, other): + print("__ifloordiv__") + return self + + def __itruediv__(self, other): + print("__itruediv__") + return self + + def __imod__(self, other): + print("__imod__") + return self + + def __ipow__(self, other): + print("__ipow__") + return self + + def __ior__(self, other): + print("__ior__") + return self + + def __ixor__(self, other): + print("__ixor__") + return self + + def __iand__(self, other): + print("__iand__") + return self + + def __ilshift__(self, other): + print("__ilshift__") + return self + + def __irshift__(self, other): + print("__irshift__") + return self + + +a = A() + +try: + a *= None +except TypeError: + print("SKIP") + raise SystemExit + +a @= None +a //= None +a /= None +a %= None +a **= None +a |= None +a ^= None +a &= None +a <<= None +a >>= None + +# Normal operator should not fallback to inplace operator +try: + a * None +except TypeError: + print("TypeError") diff --git a/tests/basics/class_inplace_op2.py.exp b/tests/basics/class_inplace_op2.py.exp new file mode 100644 index 0000000000..8c323b5178 --- /dev/null +++ b/tests/basics/class_inplace_op2.py.exp @@ -0,0 +1,12 @@ +__imul__ +__imatmul__ +__ifloordiv__ +__itruediv__ +__imod__ +__ipow__ +__ior__ +__ixor__ +__iand__ +__ilshift__ +__irshift__ +TypeError From 3e16763201d879cfdd58fdf269fbe03a3d326675 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 22 Sep 2020 14:08:22 +1000 Subject: [PATCH 069/337] stm32/rfcore: Fix FUS layout and size of ipcc_device_info_table_t. The device info table has a different layout when core 2 is in FUS mode. In particular it's larger than the 32 bytes used when in WS mode and if the correct amount of space is not allocated then the end of the table may be overwritten with other data (eg with FUS version 0.5.3). So update the structure to fix this. Also update rfcore.py to disable IRQs (which are enabled by rfcore.c), to not depend on uctypes, and to not require the asm_thumb emitter. Signed-off-by: Damien George --- ports/stm32/boards/NUCLEO_WB55/rfcore.py | 19 ++++++------ ports/stm32/rfcore.c | 38 ++++++++++++++++++------ 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/ports/stm32/boards/NUCLEO_WB55/rfcore.py b/ports/stm32/boards/NUCLEO_WB55/rfcore.py index e612499a78..cfe315605e 100644 --- a/ports/stm32/boards/NUCLEO_WB55/rfcore.py +++ b/ports/stm32/boards/NUCLEO_WB55/rfcore.py @@ -34,10 +34,15 @@ # to print out SRAM2A, register state and FUS/WS info. from machine import mem8, mem16, mem32 -import time, struct, uctypes +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 @@ -68,7 +73,7 @@ class Flash: self.wait_not_busy() cr = 1 << 0 # PG mem32[stm.FLASH + stm.FLASH_CR] = cr - buf_addr = uctypes.addressof(buf) + buf_addr = addressof(buf) off = 0 while off < len(buf): mem32[addr + off] = mem32[buf_addr + off] @@ -111,13 +116,6 @@ OCF_FUS_FW_DELETE = const(0x55) OCF_FUS_START_WS = const(0x5A) OCF_BLE_INIT = const(0x66) - -@micropython.asm_thumb -def asm_sev_wfe(): - data(2, 0xBF40) # sev - data(2, 0xBF20) # wfe - - TABLE_DEVICE_INFO = const(0) TABLE_BLE = const(1) TABLE_SYS = const(3) @@ -199,6 +197,9 @@ def ipcc_init(): BLE_EVT_QUEUE = get_ipcc_table_word(TABLE_BLE, 2) BLE_HCI_ACL_DATA_BUF = get_ipcc_table_word(TABLE_BLE, 3) + # Disable interrupts, the code here uses polling + mem32[stm.IPCC + stm.IPCC_C1CR] = 0 + print("IPCC initialised") print("SYS: 0x%08x 0x%08x" % (SYS_CMD_BUF, SYS_SYS_QUEUE)) print("BLE: 0x%08x 0x%08x 0x%08x" % (BLE_CMD_BUF, BLE_CS_BUF, BLE_EVT_QUEUE)) diff --git a/ports/stm32/rfcore.c b/ports/stm32/rfcore.c index ca2ad747c4..dc23bbe0b2 100644 --- a/ports/stm32/rfcore.c +++ b/ports/stm32/rfcore.c @@ -94,15 +94,35 @@ typedef struct _parse_hci_info_t { // [16:23] = SRAM2b (Number of 1k sectors) // [24:31] = SRAM2a (Number of 1k sectors) -typedef struct __attribute__((packed)) _ipcc_device_info_table_t { - uint32_t safeboot_version; - uint32_t fus_version; - uint32_t fus_memorysize; - uint32_t fus_info; - uint32_t fw_version; - uint32_t fw_memorysize; - uint32_t fw_infostack; - uint32_t fw_reserved; +typedef union __attribute__((packed)) _ipcc_device_info_table_t { + struct { + uint32_t table_state; + uint8_t reserved0; + uint8_t last_fus_state; + uint8_t last_ws_state; + uint8_t ws_type; + uint32_t safeboot_version; + uint32_t fus_version; + uint32_t fus_memorysize; + uint32_t ws_version; + uint32_t ws_memorysize; + uint32_t ws_ble_info; + uint32_t ws_thread_info; + uint32_t reserved1; + uint64_t uid64; + uint16_t device_id; + uint16_t pad; + } fus; + struct { + uint32_t safeboot_version; + uint32_t fus_version; + uint32_t fus_memorysize; + uint32_t fus_info; + uint32_t fw_version; + uint32_t fw_memorysize; + uint32_t fw_infostack; + uint32_t fw_reserved; + } ws; } ipcc_device_info_table_t; typedef struct __attribute__((packed)) _ipcc_ble_table_t { From 71adf506ce43b55b859f81f191ac0826928bbdd5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 22 Sep 2020 15:05:00 +1000 Subject: [PATCH 070/337] extmod/vfs: Fix lookup of entry in root dir so it fails correctly. Prior to this commit, uos.chdir('/') followed by uos.stat('noexist') would succeed that stat even though the entry did not exist (some other functions like listdir would have similar issues). This is because, if the current directory was the root and the path was relative, mp_vfs_lookup_path would return success for bad paths. Signed-off-by: Damien George --- extmod/vfs.c | 8 ++------ tests/extmod/vfs_basic.py | 8 ++++++++ tests/extmod/vfs_basic.py.exp | 12 ++++++++++++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/extmod/vfs.c b/extmod/vfs.c index d1291068ad..3cb7af1b43 100644 --- a/extmod/vfs.c +++ b/extmod/vfs.c @@ -83,12 +83,8 @@ mp_vfs_mount_t *mp_vfs_lookup_path(const char *path, const char **path_out) { } } - // if we get here then there's nothing mounted on / - - if (is_abs) { - // path began with / and was not found - return MP_VFS_NONE; - } + // if we get here then there's nothing mounted on /, so the path doesn't exist + return MP_VFS_NONE; } // a relative path within a mounted device diff --git a/tests/extmod/vfs_basic.py b/tests/extmod/vfs_basic.py index 62b2a27738..9a9ef2ca61 100644 --- a/tests/extmod/vfs_basic.py +++ b/tests/extmod/vfs_basic.py @@ -74,6 +74,14 @@ print(uos.statvfs("/")[9] >= 32) # getcwd when in root dir print(uos.getcwd()) +# test operations on the root directory with nothing mounted, they should all fail +for func in ("chdir", "listdir", "mkdir", "remove", "rmdir", "stat"): + for arg in ("x", "/x"): + try: + getattr(uos, func)(arg) + except OSError: + print(func, arg, "OSError") + # basic mounting and listdir uos.mount(Filesystem(1), "/test_mnt") print(uos.listdir()) diff --git a/tests/extmod/vfs_basic.py.exp b/tests/extmod/vfs_basic.py.exp index ebca310304..536bb4c805 100644 --- a/tests/extmod/vfs_basic.py.exp +++ b/tests/extmod/vfs_basic.py.exp @@ -1,6 +1,18 @@ (16384, 0, 0, 0, 0, 0, 0, 0, 0, 0) True / +chdir x OSError +chdir /x OSError +listdir x OSError +listdir /x OSError +mkdir x OSError +mkdir /x OSError +remove x OSError +remove /x OSError +rmdir x OSError +rmdir /x OSError +stat x OSError +stat /x OSError 1 mount False False ['test_mnt'] ('test_mnt', 16384, 0) From bada8c923195622aee80faf7acf003bc7852459d Mon Sep 17 00:00:00 2001 From: stijn Date: Tue, 22 Sep 2020 13:59:47 +0200 Subject: [PATCH 071/337] windows: Update build instructions in README. Make the instructions more complete by documenting all needed steps for starting from scratch. Also add a section for MSYS2 since the Travis build uses it as well and it's a good alternative for Cygwin. Remove the mingw32 reference since it's not readily available anymore in most Linux distros nor compiles successfully. --- ports/windows/README.md | 60 ++++++++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/ports/windows/README.md b/ports/windows/README.md index f1bd405513..8d907d1b72 100644 --- a/ports/windows/README.md +++ b/ports/windows/README.md @@ -3,46 +3,73 @@ It is based on Unix port, and expected to remain so. The port requires additional testing, debugging, and patches. Please consider to contribute. +All gcc-based builds use the gcc compiler from [Mingw-w64](mingw-w64.org), +which is the advancement of the original mingw project. The latter is +getting obsolete and is not actively supported by MicroPython. + +Build instruction assume you're in the ports/windows directory. Building on Debian/Ubuntu Linux system --------------------------------------- - sudo apt-get install gcc-mingw-w64 + sudo apt-get install python3 build-essential gcc-mingw-w64 + make -C ../../mpy-cross make CROSS_COMPILE=i686-w64-mingw32- -If for some reason the mingw-w64 crosscompiler is not available, you can try -mingw32 instead, but it comes with a really old gcc which may produce some -spurious errors (you may need to disable -Werror): - - sudo apt-get install mingw32 mingw32-binutils mingw32-runtime - make CROSS_COMPILE=i586-mingw32msvc- - Building under Cygwin --------------------- -Install following packages using cygwin's setup.exe: +Install Cygwin, then install following packages using Cygwin's setup.exe: * mingw64-i686-gcc-core * mingw64-x86_64-gcc-core * make +Also install the python3 package, or install Python globally for Windows (see below). + Build using: + make -C ../../mpy-cross CROSS_COMPILE=i686-w64-mingw32- make CROSS_COMPILE=i686-w64-mingw32- Or for 64bit: + make -C ../../mpy-cross CROSS_COMPILE=x86_64-w64-mingw32- make CROSS_COMPILE=x86_64-w64-mingw32- +Building under MSYS2 +-------------------- + +Install MSYS2 from http://repo.msys2.org/distrib, start the msys2.exe shell and +install the build tools: + + pacman -Syuu + pacman -S make mingw-w64-x86_64-gcc pkg-config python3 + +Start the mingw64.exe shell and build: + + make -C ../../mpy-cross STRIP=echo SIZE=echo + make + + Building using MS Visual Studio 2013 (or higher) ------------------------------------------------ -In the IDE, open `micropython.vcxproj` and build. +Install Python. There are several ways to do this, for example: download and install the +latest Python 3 release from https://www.python.org/downloads/windows or from +https://docs.conda.io/en/latest/miniconda.html, +or open the Microsoft Store app and search for Python and install it. + +Install Visual Studio and the C++ toolset (for recent versions: install +the free Visual Studio Community edition and the *Desktop development with C++* workload). + +In the IDE, open `micropython-cross.vcxproj` and `micropython.vcxproj` and build. To build from the command line: + msbuild ../../mpy-cross/mpy-cross.vcxproj msbuild micropython.vcxproj __Stack usage__ @@ -57,6 +84,19 @@ There are several ways to deal with this: See [issue 2927](https://github.com/micropython/micropython/issues/2927) for more information. +Running the tests +----------------- + +This is similar for all ports: + + cd ../../tests + python3 ./run-tests + +Depending on the combination of platform and Python version used it might be +needed to first set the MICROPY_MICROPYTHON environment variable to +the full path of micropython.exe. + + Running on Linux using Wine --------------------------- From ca017841d65b5f9da5eb4e6e3ca66b009bc54fc4 Mon Sep 17 00:00:00 2001 From: Iyassou Shimels Date: Wed, 23 Sep 2020 22:45:22 +0300 Subject: [PATCH 072/337] py/objstr: Make bytes(bytes_obj) return bytes_obj. Calling the bytes constructor on a bytes object returns the original bytes object. This saves allocating a new instance, and matches CPython. Signed-off-by: Iyassou Shimels --- py/objstr.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/py/objstr.c b/py/objstr.c index a276a255e5..84728e6f2d 100644 --- a/py/objstr.c +++ b/py/objstr.c @@ -205,6 +205,10 @@ STATIC mp_obj_t bytes_make_new(const mp_obj_type_t *type_in, size_t n_args, size return mp_const_empty_bytes; } + if (mp_obj_is_type(args[0], &mp_type_bytes)) { + return args[0]; + } + if (mp_obj_is_str(args[0])) { if (n_args < 2 || n_args > 3) { goto wrong_args; From 50e34f979c90584273a67ecd9189640417a60960 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 23 Sep 2020 23:17:56 +1000 Subject: [PATCH 073/337] py/objarray.h: Add mp_obj_memoryview_init() helper function. Signed-off-by: Damien George --- py/objarray.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/py/objarray.h b/py/objarray.h index 2fb6e2c915..94c31c9693 100644 --- a/py/objarray.h +++ b/py/objarray.h @@ -49,4 +49,14 @@ typedef struct _mp_obj_array_t { void *items; } mp_obj_array_t; +#if MICROPY_PY_BUILTINS_MEMORYVIEW +static inline void mp_obj_memoryview_init(mp_obj_array_t *self, size_t typecode, size_t offset, size_t len, void *items) { + self->base.type = &mp_type_memoryview; + self->typecode = typecode; + self->free = offset; + self->len = len; + self->items = items; +} +#endif + #endif // MICROPY_INCLUDED_PY_OBJARRAY_H From 81f2162ca0e926c9a4a1787a3863d94d86be0b36 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 23 Sep 2020 23:18:16 +1000 Subject: [PATCH 074/337] extmod/modbluetooth: Change module-owned bytes objects to memoryview. A read-only memoryview object is a better representation of the data, which is owned by the ubluetooth module and may change between calls to the user's irq callback function. Signed-off-by: Damien George --- docs/library/ubluetooth.rst | 22 +++++++++++----- extmod/modbluetooth.c | 26 +++++++------------ tests/multi_bluetooth/ble_characteristic.py | 6 ++--- tests/multi_bluetooth/ble_gap_device_name.py | 2 +- .../multi_bluetooth/ble_gatt_data_transfer.py | 2 +- 5 files changed, 31 insertions(+), 27 deletions(-) diff --git a/docs/library/ubluetooth.rst b/docs/library/ubluetooth.rst index 03d03583a1..f94ad3a612 100644 --- a/docs/library/ubluetooth.rst +++ b/docs/library/ubluetooth.rst @@ -88,12 +88,22 @@ Event Handling arguments, ``event`` (which will be one of the codes below) and ``data`` (which is an event-specific tuple of values). - **Note:** the ``addr``, ``adv_data``, ``char_data``, ``notify_data``, and - ``uuid`` entries in the tuples are references to data managed by the - :mod:`ubluetooth` module (i.e. the same instance will be re-used across - multiple calls to the event handler). If your program wants to use this - data outside of the handler, then it must copy them first, e.g. by using - ``bytes(addr)`` or ``bluetooth.UUID(uuid)``. + **Note:** As an optimisation to prevent unnecessary allocations, the ``addr``, + ``adv_data``, ``char_data``, ``notify_data``, and ``uuid`` entries in the + tuples are read-only memoryview instances pointing to ubluetooth's internal + ringbuffer, and are only valid during the invocation of the IRQ handler + function. If your program needs to save one of these values to access after + the IRQ handler has returned (e.g. by saving it in a class instance or global + variable), then it needs to take a copy of the data, either by using ``bytes()`` + or ``bluetooth.UUID()``, like this:: + + connected_addr = bytes(addr) # equivalently: adv_data, char_data, or notify_data + matched_uuid = bluetooth.UUID(uuid) + + For example, the IRQ handler for a scan result might inspect the ``adv_data`` + to decide if it's the correct device, and only then copy the address data to be + used elsewhere in the program. And to print data from within the IRQ handler, + ``print(bytes(addr))`` will be needed. An event handler showing all possible events:: diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index daf9cd0d19..57f69433a1 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -32,7 +32,6 @@ #include "py/mphal.h" #include "py/obj.h" #include "py/objarray.h" -#include "py/objstr.h" #include "py/qstr.h" #include "py/runtime.h" #include "extmod/modbluetooth.h" @@ -62,9 +61,8 @@ typedef struct { mp_obj_t irq_data_tuple; uint8_t irq_data_addr_bytes[6]; uint16_t irq_data_data_alloc; - uint8_t *irq_data_data_bytes; - mp_obj_str_t irq_data_addr; - mp_obj_str_t irq_data_data; + mp_obj_array_t irq_data_addr; + 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 @@ -262,11 +260,9 @@ STATIC mp_obj_t bluetooth_ble_make_new(const mp_obj_type_t *type, size_t n_args, #endif // Pre-allocated buffers for address, payload and uuid. - o->irq_data_addr.base.type = &mp_type_bytes; - o->irq_data_addr.data = o->irq_data_addr_bytes; + 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); - o->irq_data_data.base.type = &mp_type_bytes; - o->irq_data_data.data = m_new(uint8_t, o->irq_data_data_alloc); + 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; // Allocate the default ringbuf. @@ -352,7 +348,7 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map // Get old buffer sizes and pointers 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.data; + uint8_t *old_irq_data_buf = (uint8_t *)self->irq_data_data.items; size_t old_irq_data_alloc = self->irq_data_data_alloc; // Atomically update the ringbuf and irq data @@ -362,7 +358,7 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map self->ringbuf.iget = 0; self->ringbuf.iput = 0; self->irq_data_data_alloc = irq_data_alloc; - self->irq_data_data.data = irq_data; + self->irq_data_data.items = irq_data; MICROPY_PY_BLUETOOTH_EXIT // Free old buffers @@ -850,7 +846,7 @@ const mp_obj_module_t mp_module_ubluetooth = { #include -STATIC void ringbuf_extract(ringbuf_t *ringbuf, mp_obj_tuple_t *data_tuple, size_t n_u16, size_t n_u8, mp_obj_str_t *bytes_addr, size_t n_i8, mp_obj_bluetooth_uuid_t *uuid, mp_obj_str_t *bytes_data) { +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; @@ -863,8 +859,7 @@ STATIC void ringbuf_extract(ringbuf_t *ringbuf, mp_obj_tuple_t *data_tuple, size if (bytes_addr) { bytes_addr->len = 6; for (size_t i = 0; i < bytes_addr->len; ++i) { - // cast away const, this is actually bt->irq_addr_bytes. - ((uint8_t *)bytes_addr->data)[i] = ringbuf_get(ringbuf); + ((uint8_t *)bytes_addr->items)[i] = ringbuf_get(ringbuf); } data_tuple->items[j++] = MP_OBJ_FROM_PTR(bytes_addr); } @@ -880,12 +875,11 @@ STATIC void ringbuf_extract(ringbuf_t *ringbuf, mp_obj_tuple_t *data_tuple, size #endif // The code that enqueues into the ringbuf should ensure that it doesn't // put more than bt->irq_data_data_alloc bytes into the ringbuf, because - // that's what's available here in bt->irq_data_bytes. + // that's what's available here. if (bytes_data) { bytes_data->len = ringbuf_get16(ringbuf); for (size_t i = 0; i < bytes_data->len; ++i) { - // cast away const, this is actually bt->irq_data_bytes. - ((uint8_t *)bytes_data->data)[i] = ringbuf_get(ringbuf); + ((uint8_t *)bytes_data->items)[i] = ringbuf_get(ringbuf); } data_tuple->items[j++] = MP_OBJ_FROM_PTR(bytes_data); } diff --git a/tests/multi_bluetooth/ble_characteristic.py b/tests/multi_bluetooth/ble_characteristic.py index d918d9aefc..026b9d551c 100644 --- a/tests/multi_bluetooth/ble_characteristic.py +++ b/tests/multi_bluetooth/ble_characteristic.py @@ -54,15 +54,15 @@ def irq(event, data): print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) value_handle = data[2] elif event == _IRQ_GATTC_READ_RESULT: - print("_IRQ_GATTC_READ_RESULT", data[-1]) + 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_GATTC_NOTIFY: - print("_IRQ_GATTC_NOTIFY", data[-1]) + print("_IRQ_GATTC_NOTIFY", bytes(data[-1])) elif event == _IRQ_GATTC_INDICATE: - print("_IRQ_GATTC_INDICATE", data[-1]) + print("_IRQ_GATTC_INDICATE", bytes(data[-1])) elif event == _IRQ_GATTS_INDICATE_DONE: print("_IRQ_GATTS_INDICATE_DONE", data[-1]) diff --git a/tests/multi_bluetooth/ble_gap_device_name.py b/tests/multi_bluetooth/ble_gap_device_name.py index 92ea94278a..fbc9d80bae 100644 --- a/tests/multi_bluetooth/ble_gap_device_name.py +++ b/tests/multi_bluetooth/ble_gap_device_name.py @@ -36,7 +36,7 @@ def irq(event, data): print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) value_handle = data[2] elif event == _IRQ_GATTC_READ_RESULT: - print("_IRQ_GATTC_READ_RESULT", data[-1]) + print("_IRQ_GATTC_READ_RESULT", bytes(data[-1])) if waiting_event is not None: if isinstance(waiting_event, int) and event == waiting_event: diff --git a/tests/multi_bluetooth/ble_gatt_data_transfer.py b/tests/multi_bluetooth/ble_gatt_data_transfer.py index 240f048607..944c9e2d2a 100644 --- a/tests/multi_bluetooth/ble_gatt_data_transfer.py +++ b/tests/multi_bluetooth/ble_gatt_data_transfer.py @@ -67,7 +67,7 @@ def irq(event, data): elif event == _IRQ_GATTC_WRITE_DONE: print("_IRQ_GATTC_WRITE_DONE", data[-1]) elif event == _IRQ_GATTC_NOTIFY: - print("_IRQ_GATTC_NOTIFY", data[-1]) + print("_IRQ_GATTC_NOTIFY", bytes(data[-1])) if waiting_event is not None: if (isinstance(waiting_event, int) and event == waiting_event) or ( From c8ade2bd7f5d0e089098bcc07eb77a770f3da726 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 23 Sep 2020 22:47:25 +1000 Subject: [PATCH 075/337] docs/develop: Add notes on prerequisite tools for building native .mpy. Signed-off-by: Damien George --- docs/develop/natmod.rst | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/develop/natmod.rst b/docs/develop/natmod.rst index dc9f82e773..a4c188719a 100644 --- a/docs/develop/natmod.rst +++ b/docs/develop/natmod.rst @@ -21,7 +21,8 @@ language which can be compiled to stand-alone machine code can be put into a A native .mpy module is built using the ``mpy_ld.py`` tool, which is found in the ``tools/`` directory of the project. This tool takes a set of object files -(.o files) and links them together to create a native .mpy files. +(.o files) and links them together to create a native .mpy files. It requires +CPython 3 and the library pyelftools v0.25 or greater. Supported features and limitations ---------------------------------- @@ -179,6 +180,14 @@ The file ``Makefile`` contains: Compiling the module -------------------- +The prerequisite tools needed to build a native .mpy file are: + +* The MicroPython repository (at least the ``py/`` and ``tools/`` directories). +* CPython 3, and the library pyelftools (eg ``pip install 'pyelftools>=0.25'``). +* GNU make. +* A C compiler for the target architecture (if C source is used). +* Optionally ``mpy-cross``, built from the MicroPython repository (if .py source is used). + Be sure to select the correct ``ARCH`` for the target you are going to run on. Then build with:: From 9123b67d641ba708a4ea3e5cd50665f0a73c6c8a Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 24 Sep 2020 12:03:30 +1000 Subject: [PATCH 076/337] tests/run-tests: Use -BS flags when running CPython. The use of -S ensures that only the CPython standard library is accessible, which makes tests run the same regardless of any site-packages that are installed. It also improves start-up time of CPython, reducing the overall time spent running the test suite. tests/basics/containment.py is updated to work around issue with old Python versions not being able to str-format a dict-keys object, which becomes apparent when -S is used. Signed-off-by: Damien George --- tests/basics/containment.py | 4 ++-- tests/run-tests | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/basics/containment.py b/tests/basics/containment.py index 4c94a9bae4..24317ddd23 100644 --- a/tests/basics/containment.py +++ b/tests/basics/containment.py @@ -1,8 +1,8 @@ # sets, see set_containment for i in 1, 2: for o in {1:2}, {1:2}.keys(): - print("{} in {}: {}".format(i, o, i in o)) - print("{} not in {}: {}".format(i, o, i not in o)) + print("{} in {}: {}".format(i, str(o), i in o)) + print("{} not in {}: {}".format(i, str(o), i not in o)) haystack = "supercalifragilistc" for needle in [haystack[i:] for i in range(len(haystack))]: diff --git a/tests/run-tests b/tests/run-tests index a7b88ecdd3..eebc8c4252 100755 --- a/tests/run-tests +++ b/tests/run-tests @@ -26,6 +26,10 @@ else: CPYTHON3 = os.getenv('MICROPY_CPYTHON3', 'python3') MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', base_path('../ports/unix/micropython')) +# Use CPython options to not save .pyc files, to only access the core standard library +# (not site packages which may clash with u-module names), and improve start up time. +CPYTHON3_CMD = [CPYTHON3, "-BS"] + # mpy-cross is only needed if --via-mpy command-line arg is passed MPYCROSS = os.getenv('MICROPY_MPYCROSS', base_path('../mpy-cross/mpy-cross')) @@ -319,7 +323,7 @@ def run_tests(pyb, tests, args, result_dir): upy_float_precision = int(upy_float_precision) has_complex = run_feature_check(pyb, args, base_path, 'complex.py') == b'complex\n' has_coverage = run_feature_check(pyb, args, base_path, 'coverage.py') == b'coverage\n' - cpy_byteorder = subprocess.check_output([CPYTHON3, base_path('feature_check/byteorder.py')]) + cpy_byteorder = subprocess.check_output(CPYTHON3_CMD + [base_path('feature_check/byteorder.py')]) skip_endian = (upy_byteorder != cpy_byteorder) # These tests don't test slice explicitly but rather use it to perform the test @@ -498,7 +502,7 @@ def run_tests(pyb, tests, args, result_dir): else: # run CPython to work out expected output try: - output_expected = subprocess.check_output([CPYTHON3, '-B', test_file]) + output_expected = subprocess.check_output(CPYTHON3_CMD + [test_file]) if args.write_exp: with open(test_file_expected, 'wb') as f: f.write(output_expected) From 0fd0eb00aa5b9d311046d48d73a8cfabb30d7dd6 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 25 Sep 2020 03:15:22 +1000 Subject: [PATCH 077/337] examples/bluetooth: Update to use positional-only args to irq(). To match 6a6a5f9e151473bdcc1d14725d680691ff665a82. --- examples/bluetooth/ble_simple_central.py | 2 +- examples/bluetooth/ble_simple_peripheral.py | 2 +- examples/bluetooth/ble_temperature.py | 2 +- examples/bluetooth/ble_temperature_central.py | 2 +- examples/bluetooth/ble_uart_peripheral.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/bluetooth/ble_simple_central.py b/examples/bluetooth/ble_simple_central.py index f0a3297d32..a6bbdc6ee0 100644 --- a/examples/bluetooth/ble_simple_central.py +++ b/examples/bluetooth/ble_simple_central.py @@ -45,7 +45,7 @@ class BLESimpleCentral: def __init__(self, ble): self._ble = ble self._ble.active(True) - self._ble.irq(handler=self._irq) + self._ble.irq(self._irq) self._reset() diff --git a/examples/bluetooth/ble_simple_peripheral.py b/examples/bluetooth/ble_simple_peripheral.py index f5e7661926..d2b134e714 100644 --- a/examples/bluetooth/ble_simple_peripheral.py +++ b/examples/bluetooth/ble_simple_peripheral.py @@ -31,7 +31,7 @@ class BLESimplePeripheral: def __init__(self, ble, name="mpy-uart"): self._ble = ble self._ble.active(True) - self._ble.irq(handler=self._irq) + self._ble.irq(self._irq) ((self._handle_tx, self._handle_rx),) = self._ble.gatts_register_services((_UART_SERVICE,)) self._connections = set() self._write_callback = None diff --git a/examples/bluetooth/ble_temperature.py b/examples/bluetooth/ble_temperature.py index 001a26b114..d375a62ffb 100644 --- a/examples/bluetooth/ble_temperature.py +++ b/examples/bluetooth/ble_temperature.py @@ -35,7 +35,7 @@ class BLETemperature: def __init__(self, ble, name="mpy-temp"): self._ble = ble self._ble.active(True) - self._ble.irq(handler=self._irq) + self._ble.irq(self._irq) ((self._handle,),) = self._ble.gatts_register_services((_ENV_SENSE_SERVICE,)) self._connections = set() self._payload = advertising_payload( diff --git a/examples/bluetooth/ble_temperature_central.py b/examples/bluetooth/ble_temperature_central.py index 7983034178..96c4aad144 100644 --- a/examples/bluetooth/ble_temperature_central.py +++ b/examples/bluetooth/ble_temperature_central.py @@ -56,7 +56,7 @@ class BLETemperatureCentral: def __init__(self, ble): self._ble = ble self._ble.active(True) - self._ble.irq(handler=self._irq) + self._ble.irq(self._irq) self._reset() diff --git a/examples/bluetooth/ble_uart_peripheral.py b/examples/bluetooth/ble_uart_peripheral.py index 59b35f7e6a..6d167a871a 100644 --- a/examples/bluetooth/ble_uart_peripheral.py +++ b/examples/bluetooth/ble_uart_peripheral.py @@ -31,7 +31,7 @@ class BLEUART: def __init__(self, ble, name="mpy-uart", rxbuf=100): self._ble = ble self._ble.active(True) - self._ble.irq(handler=self._irq) + self._ble.irq(self._irq) ((self._tx_handle, self._rx_handle),) = self._ble.gatts_register_services((_UART_SERVICE,)) # Increase the size of the rx buffer and enable append mode. self._ble.gatts_set_buffer(self._rx_handle, rxbuf, True) From 319437d4bd7c97ab18275d3eaf1bc190554f9df7 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Fri, 25 Sep 2020 12:27:02 +1000 Subject: [PATCH 078/337] extmod/modure: Allow \\ in re.sub replacements. --- extmod/modure.c | 3 +++ tests/extmod/ure_sub.py | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/extmod/modure.c b/extmod/modure.c index 328c897d88..220587b42f 100644 --- a/extmod/modure.c +++ b/extmod/modure.c @@ -343,6 +343,9 @@ STATIC mp_obj_t re_sub_helper(size_t n_args, const mp_obj_t *args) { const char *end_match = match->caps[match_no * 2 + 1]; vstr_add_strn(&vstr_return, start_match, end_match - start_match); } + } else if (*repl == '\\') { + // Add the \ character + vstr_add_byte(&vstr_return, *repl++); } } else { // Just add the current byte from the replacement string diff --git a/tests/extmod/ure_sub.py b/tests/extmod/ure_sub.py index 953e7bf62a..ae6ad28d62 100644 --- a/tests/extmod/ure_sub.py +++ b/tests/extmod/ure_sub.py @@ -43,6 +43,9 @@ print( ) ) +# \g immediately followed by another \g +print(re.sub("(abc)", r"\g<1>\g<1>", "abc")) + # no matches at all print(re.sub("a", "b", "c")) @@ -69,3 +72,6 @@ try: re.sub(123, "a", "a") except TypeError: print("TypeError") + +# Include \ in the sub replacement +print(re.sub("b", "\\\\b", "abc")) From ce49be43b1ac7582edcfec20c56928781a512a7d Mon Sep 17 00:00:00 2001 From: Maureen Helm Date: Sat, 12 Sep 2020 14:46:05 -0500 Subject: [PATCH 079/337] zephyr: Replace zephyr integer types with C99 types. Zephyr v2.4.0 stopped using custom integer types in favor of C99 types instead. Signed-off-by: Maureen Helm --- ports/zephyr/machine_i2c.c | 2 +- ports/zephyr/zephyr_storage.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/zephyr/machine_i2c.c b/ports/zephyr/machine_i2c.c index 4b29f41d1e..576cb19777 100644 --- a/ports/zephyr/machine_i2c.c +++ b/ports/zephyr/machine_i2c.c @@ -97,7 +97,7 @@ STATIC int machine_hard_i2c_transfer_single(mp_obj_base_t *self_in, uint16_t add struct i2c_msg msg; int ret; - msg.buf = (u8_t *)buf; + msg.buf = (uint8_t *)buf; msg.len = len; msg.flags = 0; diff --git a/ports/zephyr/zephyr_storage.c b/ports/zephyr/zephyr_storage.c index 83f19a8fee..1c25b32771 100644 --- a/ports/zephyr/zephyr_storage.c +++ b/ports/zephyr/zephyr_storage.c @@ -146,7 +146,7 @@ typedef struct _zephyr_flash_area_obj_t { const struct flash_area *area; int block_size; int block_count; - u8_t id; + uint8_t id; } zephyr_flash_area_obj_t; STATIC void zephyr_flash_area_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { From f842e32155d38038f3a2657238be57724b1e0bf1 Mon Sep 17 00:00:00 2001 From: Maureen Helm Date: Sat, 12 Sep 2020 14:50:21 -0500 Subject: [PATCH 080/337] zephyr: Const-ify struct device instance pointers. Zephyr v2.4.0 added a const qualifier to usages of struct device to allow storing device driver instances exclusively in flash and thereby reduce ram footprint. Signed-off-by: Maureen Helm --- ports/zephyr/machine_i2c.c | 4 ++-- ports/zephyr/machine_pin.c | 4 ++-- ports/zephyr/modmachine.h | 2 +- ports/zephyr/modzsensor.c | 2 +- ports/zephyr/uart_core.c | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ports/zephyr/machine_i2c.c b/ports/zephyr/machine_i2c.c index 576cb19777..0efcd331fc 100644 --- a/ports/zephyr/machine_i2c.c +++ b/ports/zephyr/machine_i2c.c @@ -42,7 +42,7 @@ STATIC const mp_obj_type_t machine_hard_i2c_type; typedef struct _machine_hard_i2c_obj_t { mp_obj_base_t base; - struct device *dev; + const struct device *dev; bool restart; } machine_hard_i2c_obj_t; @@ -65,7 +65,7 @@ mp_obj_t machine_hard_i2c_make_new(const mp_obj_type_t *type, size_t n_args, siz mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); const char *dev_name = mp_obj_str_get_str(args[ARG_id].u_obj); - struct device *dev = device_get_binding(dev_name); + const struct device *dev = device_get_binding(dev_name); if (dev == NULL) { mp_raise_ValueError(MP_ERROR_TEXT("device not found")); diff --git a/ports/zephyr/machine_pin.c b/ports/zephyr/machine_pin.c index c9c6a8c893..34c822c2f0 100644 --- a/ports/zephyr/machine_pin.c +++ b/ports/zephyr/machine_pin.c @@ -58,7 +58,7 @@ void machine_pin_deinit(void) { MP_STATE_PORT(machine_pin_irq_list) = NULL; } -STATIC void gpio_callback_handler(struct device *port, struct gpio_callback *cb, gpio_port_pins_t pins) { +STATIC void gpio_callback_handler(const struct device *port, struct gpio_callback *cb, gpio_port_pins_t pins) { machine_pin_irq_obj_t *irq = CONTAINER_OF(cb, machine_pin_irq_obj_t, callback); #if MICROPY_STACK_CHECK @@ -132,7 +132,7 @@ mp_obj_t mp_pin_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, mp_obj_get_array_fixed_n(args[0], 2, &items); const char *drv_name = mp_obj_str_get_str(items[0]); int wanted_pin = mp_obj_get_int(items[1]); - struct device *wanted_port = device_get_binding(drv_name); + const struct device *wanted_port = device_get_binding(drv_name); if (!wanted_port) { mp_raise_ValueError(MP_ERROR_TEXT("invalid port")); } diff --git a/ports/zephyr/modmachine.h b/ports/zephyr/modmachine.h index 766a9d7ea3..5d7b2e1ed8 100644 --- a/ports/zephyr/modmachine.h +++ b/ports/zephyr/modmachine.h @@ -9,7 +9,7 @@ MP_DECLARE_CONST_FUN_OBJ_0(machine_info_obj); typedef struct _machine_pin_obj_t { mp_obj_base_t base; - struct device *port; + const struct device *port; uint32_t pin; struct _machine_pin_irq_obj_t *irq; } machine_pin_obj_t; diff --git a/ports/zephyr/modzsensor.c b/ports/zephyr/modzsensor.c index b01ce2693c..01f05aacd6 100644 --- a/ports/zephyr/modzsensor.c +++ b/ports/zephyr/modzsensor.c @@ -35,7 +35,7 @@ typedef struct _mp_obj_sensor_t { mp_obj_base_t base; - struct device *dev; + const struct device *dev; } mp_obj_sensor_t; STATIC mp_obj_t sensor_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { diff --git a/ports/zephyr/uart_core.c b/ports/zephyr/uart_core.c index 63ecc8289e..44bdeb5c27 100644 --- a/ports/zephyr/uart_core.c +++ b/ports/zephyr/uart_core.c @@ -53,7 +53,7 @@ void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { } } #else - static struct device *uart_console_dev; + static const struct device *uart_console_dev; if (uart_console_dev == NULL) { uart_console_dev = device_get_binding(CONFIG_UART_CONSOLE_ON_DEV_NAME); } From c2a7aac906a31b0d361b7177fae7fc88736a7705 Mon Sep 17 00:00:00 2001 From: Maureen Helm Date: Sat, 12 Sep 2020 15:35:29 -0500 Subject: [PATCH 081/337] travis: Update zephyr build to v2.4.0. Updates CI to use the latest zephyr release tag. Signed-off-by: Maureen Helm --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 45b67dc9fe..c9fcc21336 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,7 +54,7 @@ jobs: zephyrprojectrtos/ci:v0.11.8 - docker ps -a install: - - docker exec zephyr-ci west init --mr v2.3.0 /zephyrproject + - 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: From 997ec9e8ccaf4f1d8a950ef025e098e6b033c66c Mon Sep 17 00:00:00 2001 From: Maureen Helm Date: Mon, 28 Sep 2020 09:23:10 -0500 Subject: [PATCH 082/337] zephyr: Update build instructions to v2.4.0. Updates the zephyr port build instructions to use the latest zephyr release tag. Signed-off-by: Maureen Helm --- ports/zephyr/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ports/zephyr/README.md b/ports/zephyr/README.md index a2fb393d90..d69030dc2c 100644 --- a/ports/zephyr/README.md +++ b/ports/zephyr/README.md @@ -4,7 +4,7 @@ MicroPython port to Zephyr RTOS This is a work-in-progress port of MicroPython to Zephyr RTOS (http://zephyrproject.org). -This port requires Zephyr version 2.3.0, and may also work on higher +This port requires Zephyr version 2.4.0, and may also work on higher versions. All boards supported by Zephyr (with standard level of features support, like UART console) should work with MicroPython (but not all were tested). @@ -38,13 +38,13 @@ setup is correct. If you already have Zephyr installed but are having issues building the MicroPython port then try installing the correct version of Zephyr via: - $ west init zephyrproject -m https://github.com/zephyrproject-rtos/zephyr --mr v2.3.0 + $ west init zephyrproject -m https://github.com/zephyrproject-rtos/zephyr --mr v2.4.0 Alternatively, you don't have to redo the Zephyr installation to just switch from master to a tagged release, you can instead do: $ cd zephyrproject/zephyr - $ git checkout v2.3.0 + $ git checkout v2.4.0 $ west update With Zephyr installed you may then need to configure your environment, From ee7568ca8d4f1a5d09a123cf5f7a1e430b8f4c9d Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 25 Sep 2020 10:47:59 -0500 Subject: [PATCH 083/337] docs/reference/packages.rst: Fix typo, remove duplicate "port". Fixes #6485. --- docs/reference/packages.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/packages.rst b/docs/reference/packages.rst index 2854df23a0..e9adfc176e 100644 --- a/docs/reference/packages.rst +++ b/docs/reference/packages.rst @@ -188,7 +188,7 @@ Few notes: 1. Step 5 in the sequence above assumes that the distribution package is available from PyPI. If that is not the case, you would need to copy Python source files manually to ``modules/`` subdirectory - of the port port directory. (Note that upip does not support + of the port directory. (Note that upip does not support installing from e.g. version control repositories). 2. The firmware for baremetal devices usually has size restrictions, so adding too many frozen modules may overflow it. Usually, you From c711c0049e5f12cae048d2b0e77bc70e68804ea5 Mon Sep 17 00:00:00 2001 From: Mike Wadsten Date: Tue, 22 Sep 2020 11:07:20 -0500 Subject: [PATCH 084/337] py/makeversionhdr.py: Match only git tags which look like versions. Some downstream projects may use tags in their repositories for more than just designating MicroPython releases. In those cases, the makeversionhdr.py script would end up using a different tag than intended. So tell `git describe` to only match tags that look like a MicroPython version tag, such as `v1.12` or `v2.0`. --- py/makeversionhdr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/makeversionhdr.py b/py/makeversionhdr.py index 567b3b8322..970a86e6ca 100644 --- a/py/makeversionhdr.py +++ b/py/makeversionhdr.py @@ -23,7 +23,7 @@ def get_version_info_from_git(): # Note: git describe doesn't work if no tag is available try: git_tag = subprocess.check_output( - ["git", "describe", "--dirty", "--always"], + ["git", "describe", "--dirty", "--always", "--match", "v[1-9].*"], stderr=subprocess.STDOUT, universal_newlines=True, ).strip() From c35deb2625efc877b3a0d03d5654e27232b2d101 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 16 Sep 2020 13:30:48 +1000 Subject: [PATCH 085/337] extmod/machine_i2c: Rename type to SoftI2C and add custom print method. Also rename machine_i2c_type to mp_machine_soft_i2c_type. These changes make it clear that it's a soft-I2C implementation, and match SoftSPI. Signed-off-by: Damien George --- extmod/machine_i2c.c | 17 ++++++++++++----- extmod/machine_i2c.h | 3 ++- ports/esp32/modmachine.c | 2 +- ports/esp8266/modmachine.c | 2 +- ports/nrf/modules/machine/i2c.h | 2 +- ports/nrf/modules/machine/modmachine.c | 2 +- ports/nrf/mphalport.h | 2 ++ ports/stm32/modmachine.c | 2 +- ports/zephyr/modmachine.c | 2 +- 9 files changed, 22 insertions(+), 12 deletions(-) diff --git a/extmod/machine_i2c.c b/extmod/machine_i2c.c index c804cf5703..8aad001f1e 100644 --- a/extmod/machine_i2c.c +++ b/extmod/machine_i2c.c @@ -302,6 +302,12 @@ STATIC int mp_machine_i2c_writeto(mp_obj_base_t *self, uint16_t addr, const uint /******************************************************************************/ // MicroPython bindings for 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[] = { @@ -318,7 +324,7 @@ STATIC void machine_i2c_obj_init_helper(machine_i2c_obj_t *self, size_t n_args, mp_hal_i2c_init(self, args[ARG_freq].u_int); } -STATIC mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { +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) { // check the id argument, if given if (n_args > 0) { if (args[0] != MP_OBJ_NEW_SMALL_INT(-1)) { @@ -336,7 +342,7 @@ STATIC mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, s // create new soft I2C object machine_i2c_obj_t *self = m_new_obj(machine_i2c_obj_t); - self->base.type = &machine_i2c_type; + 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); @@ -700,10 +706,11 @@ STATIC const mp_machine_i2c_p_t mp_machine_soft_i2c_p = { .transfer = mp_machine_soft_i2c_transfer, }; -const mp_obj_type_t machine_i2c_type = { +const mp_obj_type_t mp_machine_soft_i2c_type = { { &mp_type_type }, - .name = MP_QSTR_I2C, - .make_new = machine_i2c_make_new, + .name = MP_QSTR_SoftI2C, + .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, }; diff --git a/extmod/machine_i2c.h b/extmod/machine_i2c.h index f951c1f214..9723069d56 100644 --- a/extmod/machine_i2c.h +++ b/extmod/machine_i2c.h @@ -27,6 +27,7 @@ #define MICROPY_INCLUDED_EXTMOD_MACHINE_I2C_H #include "py/obj.h" +#include "py/mphal.h" #define MP_MACHINE_I2C_FLAG_READ (0x01) // if not set then it's a write #define MP_MACHINE_I2C_FLAG_STOP (0x02) @@ -56,7 +57,7 @@ typedef struct _mp_machine_soft_i2c_obj_t { mp_hal_pin_obj_t sda; } mp_machine_soft_i2c_obj_t; -extern const mp_obj_type_t machine_i2c_type; +extern const mp_obj_type_t mp_machine_soft_i2c_type; extern const mp_obj_dict_t mp_machine_soft_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); diff --git a/ports/esp32/modmachine.c b/ports/esp32/modmachine.c index 82a02522f7..d56f6fcef1 100644 --- a/ports/esp32/modmachine.c +++ b/ports/esp32/modmachine.c @@ -255,7 +255,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_TouchPad), MP_ROM_PTR(&machine_touchpad_type) }, { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&machine_adc_type) }, { MP_ROM_QSTR(MP_QSTR_DAC), MP_ROM_PTR(&machine_dac_type) }, - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, { MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&machine_pwm_type) }, { MP_ROM_QSTR(MP_QSTR_RTC), MP_ROM_PTR(&machine_rtc_type) }, { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&mp_machine_soft_spi_type) }, diff --git a/ports/esp8266/modmachine.c b/ports/esp8266/modmachine.c index cb28d5b144..4917ed9b90 100644 --- a/ports/esp8266/modmachine.c +++ b/ports/esp8266/modmachine.c @@ -421,7 +421,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&machine_adc_type) }, { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&pyb_uart_type) }, #if MICROPY_PY_MACHINE_I2C - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, #endif #if MICROPY_PY_MACHINE_SPI { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_hspi_type) }, diff --git a/ports/nrf/modules/machine/i2c.h b/ports/nrf/modules/machine/i2c.h index 92194ce757..3c4fde983a 100644 --- a/ports/nrf/modules/machine/i2c.h +++ b/ports/nrf/modules/machine/i2c.h @@ -27,7 +27,7 @@ #ifndef I2C_H__ #define I2C_H__ -extern const mp_obj_type_t machine_i2c_type; +#include "extmod/machine_i2c.h" void i2c_init0(void); diff --git a/ports/nrf/modules/machine/modmachine.c b/ports/nrf/modules/machine/modmachine.c index 48730c849f..786abac7c1 100644 --- a/ports/nrf/modules/machine/modmachine.c +++ b/ports/nrf/modules/machine/modmachine.c @@ -208,7 +208,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_hard_spi_type) }, #endif #if MICROPY_PY_MACHINE_I2C - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, #endif #if MICROPY_PY_MACHINE_ADC { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&machine_adc_type) }, diff --git a/ports/nrf/mphalport.h b/ports/nrf/mphalport.h index 15b37b7ef2..5900559b5d 100644 --- a/ports/nrf/mphalport.h +++ b/ports/nrf/mphalport.h @@ -54,8 +54,10 @@ void mp_hal_delay_us(mp_uint_t us); const char *nrfx_error_code_lookup(uint32_t err_code); +#define MP_HAL_PIN_FMT "%q" #define mp_hal_pin_obj_t const pin_obj_t * #define mp_hal_get_pin_obj(o) pin_find(o) +#define mp_hal_pin_name(p) ((p)->name) #define mp_hal_pin_high(p) nrf_gpio_pin_set(p->pin) #define mp_hal_pin_low(p) nrf_gpio_pin_clear(p->pin) #define mp_hal_pin_read(p) (nrf_gpio_pin_dir_get(p->pin) == NRF_GPIO_PIN_DIR_OUTPUT) ? nrf_gpio_pin_out_read(p->pin) : nrf_gpio_pin_read(p->pin) diff --git a/ports/stm32/modmachine.c b/ports/stm32/modmachine.c index ac4d871239..ff3b842c87 100644 --- a/ports/stm32/modmachine.c +++ b/ports/stm32/modmachine.c @@ -415,7 +415,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_RTC), MP_ROM_PTR(&pyb_rtc_type) }, { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&machine_adc_type) }, #if MICROPY_PY_MACHINE_I2C - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, #endif { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_hard_spi_type) }, { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&pyb_uart_type) }, diff --git a/ports/zephyr/modmachine.c b/ports/zephyr/modmachine.c index 72078cf9de..fc4d3b3ca8 100644 --- a/ports/zephyr/modmachine.c +++ b/ports/zephyr/modmachine.c @@ -60,7 +60,7 @@ 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) }, - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, { 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 aaed33896b0fec67a0e2ec7daf3fe908253d8cf7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 16 Sep 2020 13:37:31 +1000 Subject: [PATCH 086/337] extmod/machine_i2c: Remove "id" arg in SoftI2C constructor. The SoftI2C constructor is now used soley to create SoftI2C instances, it can no longer delegate to create a hardware-based I2C instance. Signed-off-by: Damien George --- extmod/machine_i2c.c | 15 --------------- ports/esp32/mpconfigport.h | 1 - ports/nrf/mpconfigport.h | 1 - ports/stm32/mpconfigport.h | 3 --- ports/zephyr/mpconfigport.h | 1 - 5 files changed, 21 deletions(-) diff --git a/extmod/machine_i2c.c b/extmod/machine_i2c.c index 8aad001f1e..9203f16f6d 100644 --- a/extmod/machine_i2c.c +++ b/extmod/machine_i2c.c @@ -325,21 +325,6 @@ STATIC void machine_i2c_obj_init_helper(machine_i2c_obj_t *self, size_t n_args, } 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) { - // check the id argument, if given - if (n_args > 0) { - if (args[0] != MP_OBJ_NEW_SMALL_INT(-1)) { - #if defined(MICROPY_PY_MACHINE_I2C_MAKE_NEW) - // dispatch to port-specific constructor - extern mp_obj_t MICROPY_PY_MACHINE_I2C_MAKE_NEW(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); - return MICROPY_PY_MACHINE_I2C_MAKE_NEW(type, n_args, n_kw, args); - #else - mp_raise_ValueError(MP_ERROR_TEXT("invalid I2C peripheral")); - #endif - } - --n_args; - ++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; diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 663fed3f68..00abab3afd 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -145,7 +145,6 @@ #define MICROPY_PY_MACHINE_PIN_MAKE_NEW mp_pin_make_new #define MICROPY_PY_MACHINE_PULSE (1) #define MICROPY_PY_MACHINE_I2C (1) -#define MICROPY_PY_MACHINE_I2C_MAKE_NEW machine_hw_i2c_make_new #define MICROPY_PY_MACHINE_SPI (1) #define MICROPY_PY_MACHINE_SPI_MSB (0) #define MICROPY_PY_MACHINE_SPI_LSB (1) diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index 76bb6d7bdf..a025feb2ec 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -135,7 +135,6 @@ #define MICROPY_PY_UTIME_MP_HAL (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_PULSE (0) -#define MICROPY_PY_MACHINE_I2C_MAKE_NEW machine_hard_i2c_make_new #define MICROPY_PY_MACHINE_SPI (0) #define MICROPY_PY_MACHINE_SPI_MIN_DELAY (0) #define MICROPY_PY_FRAMEBUF (0) diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 95b539603f..9ff4765ac2 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -183,9 +183,6 @@ #define MICROPY_PY_MACHINE_PULSE (1) #define MICROPY_PY_MACHINE_PIN_MAKE_NEW mp_pin_make_new #define MICROPY_PY_MACHINE_I2C (1) -#if MICROPY_HW_ENABLE_HW_I2C -#define MICROPY_PY_MACHINE_I2C_MAKE_NEW machine_hard_i2c_make_new -#endif #define MICROPY_PY_MACHINE_SPI (1) #define MICROPY_PY_MACHINE_SPI_MSB (SPI_FIRSTBIT_MSB) #define MICROPY_PY_MACHINE_SPI_LSB (SPI_FIRSTBIT_LSB) diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index f94ee72707..6bfd9ff884 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -61,7 +61,6 @@ #define MICROPY_PY_MICROPYTHON_MEM_INFO (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_I2C (1) -#define MICROPY_PY_MACHINE_I2C_MAKE_NEW machine_hard_i2c_make_new #define MICROPY_PY_MACHINE_PIN_MAKE_NEW mp_pin_make_new #define MICROPY_MODULE_WEAK_LINKS (1) #define MICROPY_PY_STRUCT (0) From 9e0533b9e158a455be9284b70011d0515096a5f6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 16 Sep 2020 14:07:39 +1000 Subject: [PATCH 087/337] extmod/machine_spi: Remove "id" arg in SoftSPI constructor. The SoftSPI constructor is now used soley to create SoftSPI instances, it can no longer delegate to create a hardware-based SPI instance. Signed-off-by: Damien George --- extmod/machine_spi.c | 24 +----------------------- ports/esp32/mpconfigport.h | 1 - ports/esp8266/machine_hspi.c | 2 +- ports/esp8266/mpconfigport.h | 1 - ports/stm32/machine_spi.c | 4 ++-- ports/stm32/mpconfigport.h | 1 - 6 files changed, 4 insertions(+), 29 deletions(-) diff --git a/extmod/machine_spi.c b/extmod/machine_spi.c index a7f96573a7..c951a5137c 100644 --- a/extmod/machine_spi.c +++ b/extmod/machine_spi.c @@ -41,28 +41,6 @@ /******************************************************************************/ // MicroPython bindings for generic machine.SPI -STATIC mp_obj_t mp_machine_soft_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); - -mp_obj_t mp_machine_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { - // check the id argument, if given - if (n_args > 0) { - if (args[0] != MP_OBJ_NEW_SMALL_INT(-1)) { - #if defined(MICROPY_PY_MACHINE_SPI_MAKE_NEW) - // dispatch to port-specific constructor - extern mp_obj_t MICROPY_PY_MACHINE_SPI_MAKE_NEW(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); - return MICROPY_PY_MACHINE_SPI_MAKE_NEW(type, n_args, n_kw, args); - #else - mp_raise_ValueError(MP_ERROR_TEXT("invalid SPI peripheral")); - #endif - } - --n_args; - ++args; - } - - // software SPI - return mp_machine_soft_spi_make_new(type, n_args, n_kw, args); -} - STATIC mp_obj_t machine_spi_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { mp_obj_base_t *s = (mp_obj_base_t *)MP_OBJ_TO_PTR(args[0]); mp_machine_spi_p_t *spi_p = (mp_machine_spi_p_t *)s->type->protocol; @@ -275,7 +253,7 @@ const mp_obj_type_t mp_machine_soft_spi_type = { { &mp_type_type }, .name = MP_QSTR_SoftSPI, .print = mp_machine_soft_spi_print, - .make_new = mp_machine_spi_make_new, // delegate to master constructor + .make_new = mp_machine_soft_spi_make_new, .protocol = &mp_machine_soft_spi_p, .locals_dict = (mp_obj_dict_t *)&mp_machine_spi_locals_dict, }; diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 00abab3afd..5bf0676b23 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -148,7 +148,6 @@ #define MICROPY_PY_MACHINE_SPI (1) #define MICROPY_PY_MACHINE_SPI_MSB (0) #define MICROPY_PY_MACHINE_SPI_LSB (1) -#define MICROPY_PY_MACHINE_SPI_MAKE_NEW machine_hw_spi_make_new #define MICROPY_HW_ENABLE_SDCARD (1) #define MICROPY_HW_SOFTSPI_MIN_DELAY (0) #define MICROPY_HW_SOFTSPI_MAX_BAUDRATE (ets_get_cpu_frequency() * 1000000 / 200) // roughly diff --git a/ports/esp8266/machine_hspi.c b/ports/esp8266/machine_hspi.c index 7319194d76..55bbcf9f58 100644 --- a/ports/esp8266/machine_hspi.c +++ b/ports/esp8266/machine_hspi.c @@ -178,7 +178,7 @@ const mp_obj_type_t machine_hspi_type = { { &mp_type_type }, .name = MP_QSTR_HSPI, .print = machine_hspi_print, - .make_new = mp_machine_spi_make_new, // delegate to master constructor + .make_new = machine_hspi_make_new, .protocol = &machine_hspi_p, .locals_dict = (mp_obj_dict_t *)&mp_machine_spi_locals_dict, }; diff --git a/ports/esp8266/mpconfigport.h b/ports/esp8266/mpconfigport.h index 5344a98d6a..974310f84d 100644 --- a/ports/esp8266/mpconfigport.h +++ b/ports/esp8266/mpconfigport.h @@ -80,7 +80,6 @@ #define MICROPY_PY_MACHINE_PULSE (1) #define MICROPY_PY_MACHINE_I2C (1) #define MICROPY_PY_MACHINE_SPI (1) -#define MICROPY_PY_MACHINE_SPI_MAKE_NEW machine_hspi_make_new #define MICROPY_PY_UWEBSOCKET (1) #define MICROPY_PY_WEBREPL (1) #define MICROPY_PY_WEBREPL_DELAY (20) diff --git a/ports/stm32/machine_spi.c b/ports/stm32/machine_spi.c index cf6e96ab67..edbd500b3c 100644 --- a/ports/stm32/machine_spi.c +++ b/ports/stm32/machine_spi.c @@ -48,7 +48,7 @@ STATIC void machine_hard_spi_print(const mp_print_t *print, mp_obj_t self_in, mp mp_obj_t machine_hard_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum { ARG_id, ARG_baudrate, ARG_polarity, ARG_phase, ARG_bits, ARG_firstbit, ARG_sck, ARG_mosi, ARG_miso }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_id, MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_SMALL_INT(-1)} }, + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = 500000} }, { MP_QSTR_polarity, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, { MP_QSTR_phase, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, @@ -137,7 +137,7 @@ const mp_obj_type_t machine_hard_spi_type = { { &mp_type_type }, .name = MP_QSTR_SPI, .print = machine_hard_spi_print, - .make_new = mp_machine_spi_make_new, // delegate to master constructor + .make_new = machine_hard_spi_make_new, .protocol = &machine_hard_spi_p, .locals_dict = (mp_obj_dict_t *)&mp_machine_spi_locals_dict, }; diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 9ff4765ac2..7ea41bb6f4 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -186,7 +186,6 @@ #define MICROPY_PY_MACHINE_SPI (1) #define MICROPY_PY_MACHINE_SPI_MSB (SPI_FIRSTBIT_MSB) #define MICROPY_PY_MACHINE_SPI_LSB (SPI_FIRSTBIT_LSB) -#define MICROPY_PY_MACHINE_SPI_MAKE_NEW machine_hard_spi_make_new #define MICROPY_HW_SOFTSPI_MIN_DELAY (0) #define MICROPY_HW_SOFTSPI_MAX_BAUDRATE (HAL_RCC_GetSysClockFreq() / 48) #define MICROPY_PY_UWEBSOCKET (MICROPY_PY_LWIP) From 39d50d129ce428858332523548f0594503d0f45b Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 16 Sep 2020 14:09:49 +1000 Subject: [PATCH 088/337] ports: Add SoftI2C and SoftSPI to machine module where appropriate. Previous commits removed the ability for one I2C/SPI constructor to construct both software- or hardware-based peripheral instances. Such construction is now split to explicit soft and non-soft types. This commit makes both types available in all ports that previously could create both software and hardware peripherals: machine.I2C and machine.SPI construct hardware instances, while machine.SoftI2C and machine.SoftSPI create software instances. This is a breaking change for use of software-based I2C and SPI. Code that constructed I2C/SPI peripherals in the following way will need to be changed: machine.I2C(-1, ...) -> machine.SoftI2C(...) machine.I2C(scl=scl, sda=sda) -> machine.SoftI2C(scl=scl, sda=sda) machine.SPI(-1, ...) -> machine.SoftSPI(...) machine.SPI(sck=sck, mosi=mosi, miso=miso) -> machine.SoftSPI(sck=sck, mosi=mosi, miso=miso) Code which uses machine.I2C and machine.SPI classes to access hardware peripherals does not need to change. Signed-off-by: Damien George --- ports/esp32/machine_i2c.c | 4 ++-- ports/esp32/modmachine.c | 6 ++++-- ports/esp32/modmachine.h | 1 + ports/esp8266/modmachine.c | 3 +++ ports/nrf/modules/machine/i2c.c | 4 +--- ports/nrf/modules/machine/i2c.h | 2 ++ ports/nrf/modules/machine/modmachine.c | 3 ++- ports/stm32/machine_i2c.c | 5 ++--- ports/stm32/modmachine.c | 7 +++++++ ports/stm32/modmachine.h | 1 + ports/stm32/spi.h | 1 - ports/zephyr/machine_i2c.c | 5 ++--- ports/zephyr/modmachine.c | 2 +- ports/zephyr/modmachine.h | 1 + ports/zephyr/mphalport.h | 4 ++++ 15 files changed, 33 insertions(+), 16 deletions(-) diff --git a/ports/esp32/machine_i2c.c b/ports/esp32/machine_i2c.c index 8362ed5e7b..075d0ded6c 100644 --- a/ports/esp32/machine_i2c.c +++ b/ports/esp32/machine_i2c.c @@ -28,6 +28,7 @@ #include "py/mphal.h" #include "py/mperrno.h" #include "extmod/machine_i2c.h" +#include "modmachine.h" #include "driver/i2c.h" @@ -45,7 +46,6 @@ typedef struct _machine_hw_i2c_obj_t { gpio_num_t sda : 8; } machine_hw_i2c_obj_t; -STATIC const mp_obj_type_t machine_hw_i2c_type; STATIC machine_hw_i2c_obj_t machine_hw_i2c_obj[I2C_NUM_MAX]; STATIC void machine_hw_i2c_init(machine_hw_i2c_obj_t *self, uint32_t freq, uint32_t timeout_us, bool first_init) { @@ -169,7 +169,7 @@ STATIC const mp_machine_i2c_p_t machine_hw_i2c_p = { .transfer = machine_hw_i2c_transfer, }; -STATIC const mp_obj_type_t machine_hw_i2c_type = { +const mp_obj_type_t machine_hw_i2c_type = { { &mp_type_type }, .name = MP_QSTR_I2C, .print = machine_hw_i2c_print, diff --git a/ports/esp32/modmachine.c b/ports/esp32/modmachine.c index d56f6fcef1..303d25ee8b 100644 --- a/ports/esp32/modmachine.c +++ b/ports/esp32/modmachine.c @@ -255,10 +255,12 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_TouchPad), MP_ROM_PTR(&machine_touchpad_type) }, { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&machine_adc_type) }, { MP_ROM_QSTR(MP_QSTR_DAC), MP_ROM_PTR(&machine_dac_type) }, - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_hw_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, { MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&machine_pwm_type) }, { MP_ROM_QSTR(MP_QSTR_RTC), MP_ROM_PTR(&machine_rtc_type) }, - { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&mp_machine_soft_spi_type) }, + { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_hw_spi_type) }, + { MP_ROM_QSTR(MP_QSTR_SoftSPI), MP_ROM_PTR(&mp_machine_soft_spi_type) }, { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&machine_uart_type) }, // Reset reasons diff --git a/ports/esp32/modmachine.h b/ports/esp32/modmachine.h index fbb43250ff..98b36e32b0 100644 --- a/ports/esp32/modmachine.h +++ b/ports/esp32/modmachine.h @@ -16,6 +16,7 @@ extern const mp_obj_type_t machine_touchpad_type; extern const mp_obj_type_t machine_adc_type; extern const mp_obj_type_t machine_dac_type; extern const mp_obj_type_t machine_pwm_type; +extern const mp_obj_type_t machine_hw_i2c_type; extern const mp_obj_type_t machine_hw_spi_type; extern const mp_obj_type_t machine_uart_type; extern const mp_obj_type_t machine_rtc_type; diff --git a/ports/esp8266/modmachine.c b/ports/esp8266/modmachine.c index 4917ed9b90..86c1d728d5 100644 --- a/ports/esp8266/modmachine.c +++ b/ports/esp8266/modmachine.c @@ -39,6 +39,7 @@ #include "extmod/machine_signal.h" #include "extmod/machine_pulse.h" #include "extmod/machine_i2c.h" +#include "extmod/machine_spi.h" #include "modmachine.h" #include "xtirq.h" @@ -422,9 +423,11 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&pyb_uart_type) }, #if MICROPY_PY_MACHINE_I2C { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, #endif #if MICROPY_PY_MACHINE_SPI { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_hspi_type) }, + { MP_ROM_QSTR(MP_QSTR_SoftSPI), MP_ROM_PTR(&mp_machine_soft_spi_type) }, #endif // wake abilities diff --git a/ports/nrf/modules/machine/i2c.c b/ports/nrf/modules/machine/i2c.c index afa906c359..704d8615a8 100644 --- a/ports/nrf/modules/machine/i2c.c +++ b/ports/nrf/modules/machine/i2c.c @@ -63,8 +63,6 @@ #endif -STATIC const mp_obj_type_t machine_hard_i2c_type; - typedef struct _machine_hard_i2c_obj_t { mp_obj_base_t base; nrfx_twi_t p_twi; // Driver instance @@ -161,7 +159,7 @@ STATIC const mp_machine_i2c_p_t machine_hard_i2c_p = { .transfer_single = machine_hard_i2c_transfer_single, }; -STATIC const mp_obj_type_t machine_hard_i2c_type = { +const mp_obj_type_t machine_hard_i2c_type = { { &mp_type_type }, .name = MP_QSTR_I2C, .print = machine_hard_i2c_print, diff --git a/ports/nrf/modules/machine/i2c.h b/ports/nrf/modules/machine/i2c.h index 3c4fde983a..1dfb1f077a 100644 --- a/ports/nrf/modules/machine/i2c.h +++ b/ports/nrf/modules/machine/i2c.h @@ -29,6 +29,8 @@ #include "extmod/machine_i2c.h" +extern const mp_obj_type_t machine_hard_i2c_type; + void i2c_init0(void); #endif // I2C_H__ diff --git a/ports/nrf/modules/machine/modmachine.c b/ports/nrf/modules/machine/modmachine.c index 786abac7c1..3330349cd5 100644 --- a/ports/nrf/modules/machine/modmachine.c +++ b/ports/nrf/modules/machine/modmachine.c @@ -208,7 +208,8 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_hard_spi_type) }, #endif #if MICROPY_PY_MACHINE_I2C - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_hard_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, #endif #if MICROPY_PY_MACHINE_ADC { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&machine_adc_type) }, diff --git a/ports/stm32/machine_i2c.c b/ports/stm32/machine_i2c.c index 14dd88b78f..31defcb17a 100644 --- a/ports/stm32/machine_i2c.c +++ b/ports/stm32/machine_i2c.c @@ -32,11 +32,10 @@ #include "py/mperrno.h" #include "extmod/machine_i2c.h" #include "i2c.h" +#include "modmachine.h" #if MICROPY_HW_ENABLE_HW_I2C -STATIC const mp_obj_type_t machine_hard_i2c_type; - #define I2C_POLL_DEFAULT_TIMEOUT_US (50000) // 50ms #if defined(STM32F0) || defined(STM32F4) || defined(STM32F7) @@ -266,7 +265,7 @@ STATIC const mp_machine_i2c_p_t machine_hard_i2c_p = { .transfer = machine_hard_i2c_transfer, }; -STATIC const mp_obj_type_t machine_hard_i2c_type = { +const mp_obj_type_t machine_hard_i2c_type = { { &mp_type_type }, .name = MP_QSTR_I2C, .print = machine_hard_i2c_print, diff --git a/ports/stm32/modmachine.c b/ports/stm32/modmachine.c index ff3b842c87..9fa00a9151 100644 --- a/ports/stm32/modmachine.c +++ b/ports/stm32/modmachine.c @@ -37,6 +37,7 @@ #include "extmod/machine_signal.h" #include "extmod/machine_pulse.h" #include "extmod/machine_i2c.h" +#include "extmod/machine_spi.h" #include "lib/utils/pyexec.h" #include "lib/oofatfs/ff.h" #include "extmod/vfs.h" @@ -415,9 +416,15 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_RTC), MP_ROM_PTR(&pyb_rtc_type) }, { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&machine_adc_type) }, #if MICROPY_PY_MACHINE_I2C + #if MICROPY_HW_ENABLE_HW_I2C + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_hard_i2c_type) }, + #else { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, #endif + { MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, + #endif { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_hard_spi_type) }, + { MP_ROM_QSTR(MP_QSTR_SoftSPI), MP_ROM_PTR(&mp_machine_soft_spi_type) }, { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&pyb_uart_type) }, { MP_ROM_QSTR(MP_QSTR_WDT), MP_ROM_PTR(&pyb_wdt_type) }, { MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&machine_timer_type) }, diff --git a/ports/stm32/modmachine.h b/ports/stm32/modmachine.h index e4ccf63885..e846208540 100644 --- a/ports/stm32/modmachine.h +++ b/ports/stm32/modmachine.h @@ -30,6 +30,7 @@ extern const mp_obj_type_t machine_adc_type; extern const mp_obj_type_t machine_timer_type; +extern const mp_obj_type_t machine_hard_i2c_type; void machine_init(void); void machine_deinit(void); diff --git a/ports/stm32/spi.h b/ports/stm32/spi.h index 885fb0bd6f..17f1bf6c4a 100644 --- a/ports/stm32/spi.h +++ b/ports/stm32/spi.h @@ -65,7 +65,6 @@ extern const spi_t spi_obj[6]; extern const mp_spi_proto_t spi_proto; extern const mp_obj_type_t pyb_spi_type; -extern const mp_obj_type_t machine_soft_spi_type; extern const mp_obj_type_t machine_hard_spi_type; // A transfer of "len" bytes should take len*8*1000/baudrate milliseconds. diff --git a/ports/zephyr/machine_i2c.c b/ports/zephyr/machine_i2c.c index 0efcd331fc..2d1c7d5c54 100644 --- a/ports/zephyr/machine_i2c.c +++ b/ports/zephyr/machine_i2c.c @@ -37,8 +37,7 @@ #include "py/mphal.h" #include "py/mperrno.h" #include "extmod/machine_i2c.h" - -STATIC const mp_obj_type_t machine_hard_i2c_type; +#include "modmachine.h" typedef struct _machine_hard_i2c_obj_t { mp_obj_base_t base; @@ -127,7 +126,7 @@ STATIC const mp_machine_i2c_p_t machine_hard_i2c_p = { .transfer_single = machine_hard_i2c_transfer_single, }; -STATIC const mp_obj_type_t machine_hard_i2c_type = { +const mp_obj_type_t machine_hard_i2c_type = { { &mp_type_type }, .name = MP_QSTR_I2C, .print = machine_hard_i2c_print, diff --git a/ports/zephyr/modmachine.c b/ports/zephyr/modmachine.c index fc4d3b3ca8..29e6c889c4 100644 --- a/ports/zephyr/modmachine.c +++ b/ports/zephyr/modmachine.c @@ -60,7 +60,7 @@ 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) }, - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_hard_i2c_type) }, { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&machine_pin_type) }, { MP_ROM_QSTR(MP_QSTR_Signal), MP_ROM_PTR(&machine_signal_type) }, diff --git a/ports/zephyr/modmachine.h b/ports/zephyr/modmachine.h index 5d7b2e1ed8..b67da55338 100644 --- a/ports/zephyr/modmachine.h +++ b/ports/zephyr/modmachine.h @@ -4,6 +4,7 @@ #include "py/obj.h" extern const mp_obj_type_t machine_pin_type; +extern const mp_obj_type_t machine_hard_i2c_type; MP_DECLARE_CONST_FUN_OBJ_0(machine_info_obj); diff --git a/ports/zephyr/mphalport.h b/ports/zephyr/mphalport.h index 9ef213ddb0..b3154b649b 100644 --- a/ports/zephyr/mphalport.h +++ b/ports/zephyr/mphalport.h @@ -30,6 +30,10 @@ static inline uint64_t mp_hal_time_ns(void) { } #define mp_hal_delay_us_fast(us) (mp_hal_delay_us(us)) + +// C-level pin API is not currently implemented +#define MP_HAL_PIN_FMT "%d" +#define mp_hal_pin_name(p) (0) #define mp_hal_pin_od_low(p) (mp_raise_NotImplementedError("mp_hal_pin_od_low")) #define mp_hal_pin_od_high(p) (mp_raise_NotImplementedError("mp_hal_pin_od_high")) #define mp_hal_pin_open_drain(p) (mp_raise_NotImplementedError("mp_hal_pin_open_drain")) From 71f3ade770fa7f2637d94f5ba5840b64a16a95db Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 25 Sep 2020 14:47:34 +1000 Subject: [PATCH 089/337] ports: Support legacy soft I2C/SPI construction via id=-1 arg. With a warning that this way of constructing software I2C/SPI is deprecated. The check and warning will be removed in a future release. This should help existing code to migrate to the new SoftI2C/SoftSPI types. Signed-off-by: Damien George --- extmod/machine_i2c.h | 13 +++++++++++++ extmod/machine_spi.h | 13 +++++++++++++ ports/esp32/machine_hw_spi.c | 2 ++ ports/esp32/machine_i2c.c | 2 ++ ports/esp8266/machine_hspi.c | 2 ++ ports/nrf/modules/machine/i2c.c | 2 ++ ports/stm32/machine_i2c.c | 2 ++ ports/stm32/machine_spi.c | 2 ++ ports/zephyr/machine_i2c.c | 2 ++ 9 files changed, 40 insertions(+) diff --git a/extmod/machine_i2c.h b/extmod/machine_i2c.h index 9723069d56..e3a87e282a 100644 --- a/extmod/machine_i2c.h +++ b/extmod/machine_i2c.h @@ -29,6 +29,19 @@ #include "py/obj.h" #include "py/mphal.h" +// Temporary support for legacy construction of SoftI2C via I2C type. +#define MP_MACHINE_I2C_CHECK_FOR_LEGACY_SOFTI2C_CONSTRUCTION(n_args, n_kw, all_args) \ + do { \ + if (n_args == 0 || all_args[0] == MP_OBJ_NEW_SMALL_INT(-1)) { \ + mp_print_str(MICROPY_ERROR_PRINTER, "Warning: I2C(-1, ...) is deprecated, use SoftI2C(...) instead\n"); \ + if (n_args != 0) { \ + --n_args; \ + ++all_args; \ + } \ + return mp_machine_soft_i2c_type.make_new(&mp_machine_soft_i2c_type, n_args, n_kw, all_args); \ + } \ + } while (0) + #define MP_MACHINE_I2C_FLAG_READ (0x01) // if not set then it's a write #define MP_MACHINE_I2C_FLAG_STOP (0x02) diff --git a/extmod/machine_spi.h b/extmod/machine_spi.h index db21e1cd31..ca92c719a8 100644 --- a/extmod/machine_spi.h +++ b/extmod/machine_spi.h @@ -30,6 +30,19 @@ #include "py/mphal.h" #include "drivers/bus/spi.h" +// Temporary support for legacy construction of SoftSPI via SPI type. +#define MP_MACHINE_SPI_CHECK_FOR_LEGACY_SOFTSPI_CONSTRUCTION(n_args, n_kw, all_args) \ + do { \ + if (n_args == 0 || all_args[0] == MP_OBJ_NEW_SMALL_INT(-1)) { \ + mp_print_str(MICROPY_ERROR_PRINTER, "Warning: SPI(-1, ...) is deprecated, use SoftSPI(...) instead\n"); \ + if (n_args != 0) { \ + --n_args; \ + ++all_args; \ + } \ + return mp_machine_soft_spi_type.make_new(&mp_machine_soft_spi_type, n_args, n_kw, all_args); \ + } \ + } while (0) + // SPI protocol typedef struct _mp_machine_spi_p_t { void (*init)(mp_obj_base_t *obj, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); diff --git a/ports/esp32/machine_hw_spi.c b/ports/esp32/machine_hw_spi.c index 3962e26b78..3790b4e0cf 100644 --- a/ports/esp32/machine_hw_spi.c +++ b/ports/esp32/machine_hw_spi.c @@ -351,6 +351,8 @@ STATIC void machine_hw_spi_init(mp_obj_base_t *self_in, size_t n_args, const mp_ } mp_obj_t machine_hw_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + MP_MACHINE_SPI_CHECK_FOR_LEGACY_SOFTSPI_CONSTRUCTION(n_args, n_kw, all_args); + enum { ARG_id, ARG_baudrate, ARG_polarity, ARG_phase, ARG_bits, ARG_firstbit, ARG_sck, ARG_mosi, ARG_miso }; static const mp_arg_t allowed_args[] = { { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, diff --git a/ports/esp32/machine_i2c.c b/ports/esp32/machine_i2c.c index 075d0ded6c..a1d0ad0f4d 100644 --- a/ports/esp32/machine_i2c.c +++ b/ports/esp32/machine_i2c.c @@ -115,6 +115,8 @@ STATIC void machine_hw_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_p } mp_obj_t machine_hw_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + MP_MACHINE_I2C_CHECK_FOR_LEGACY_SOFTI2C_CONSTRUCTION(n_args, n_kw, all_args); + // Parse args enum { ARG_id, ARG_scl, ARG_sda, ARG_freq, ARG_timeout }; static const mp_arg_t allowed_args[] = { diff --git a/ports/esp8266/machine_hspi.c b/ports/esp8266/machine_hspi.c index 55bbcf9f58..ff3ba17255 100644 --- a/ports/esp8266/machine_hspi.c +++ b/ports/esp8266/machine_hspi.c @@ -151,6 +151,8 @@ STATIC void machine_hspi_init(mp_obj_base_t *self_in, size_t n_args, const mp_ob } mp_obj_t machine_hspi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + MP_MACHINE_SPI_CHECK_FOR_LEGACY_SOFTSPI_CONSTRUCTION(n_args, n_kw, args); + // args[0] holds the id of the peripheral if (args[0] != MP_OBJ_NEW_SMALL_INT(1)) { // FlashROM is on SPI0, so far we don't support its usage diff --git a/ports/nrf/modules/machine/i2c.c b/ports/nrf/modules/machine/i2c.c index 704d8615a8..ab4d516621 100644 --- a/ports/nrf/modules/machine/i2c.c +++ b/ports/nrf/modules/machine/i2c.c @@ -94,6 +94,8 @@ STATIC void machine_hard_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp /* MicroPython bindings for machine API */ mp_obj_t machine_hard_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + MP_MACHINE_I2C_CHECK_FOR_LEGACY_SOFTI2C_CONSTRUCTION(n_args, n_kw, all_args); + enum { ARG_id, ARG_scl, ARG_sda }; static const mp_arg_t allowed_args[] = { { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, diff --git a/ports/stm32/machine_i2c.c b/ports/stm32/machine_i2c.c index 31defcb17a..e0c408c6d0 100644 --- a/ports/stm32/machine_i2c.c +++ b/ports/stm32/machine_i2c.c @@ -193,6 +193,8 @@ STATIC void machine_hard_i2c_init(machine_hard_i2c_obj_t *self, uint32_t freq, u #endif mp_obj_t machine_hard_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + MP_MACHINE_I2C_CHECK_FOR_LEGACY_SOFTI2C_CONSTRUCTION(n_args, n_kw, all_args); + // parse args enum { ARG_id, ARG_scl, ARG_sda, ARG_freq, ARG_timeout, ARG_timingr }; static const mp_arg_t allowed_args[] = { diff --git a/ports/stm32/machine_spi.c b/ports/stm32/machine_spi.c index edbd500b3c..37c026cefc 100644 --- a/ports/stm32/machine_spi.c +++ b/ports/stm32/machine_spi.c @@ -46,6 +46,8 @@ STATIC void machine_hard_spi_print(const mp_print_t *print, mp_obj_t self_in, mp } mp_obj_t machine_hard_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + MP_MACHINE_SPI_CHECK_FOR_LEGACY_SOFTSPI_CONSTRUCTION(n_args, n_kw, all_args); + enum { ARG_id, ARG_baudrate, ARG_polarity, ARG_phase, ARG_bits, ARG_firstbit, ARG_sck, ARG_mosi, ARG_miso }; static const mp_arg_t allowed_args[] = { { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, diff --git a/ports/zephyr/machine_i2c.c b/ports/zephyr/machine_i2c.c index 2d1c7d5c54..ec4b8620a5 100644 --- a/ports/zephyr/machine_i2c.c +++ b/ports/zephyr/machine_i2c.c @@ -51,6 +51,8 @@ STATIC void machine_hard_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp } mp_obj_t machine_hard_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + MP_MACHINE_I2C_CHECK_FOR_LEGACY_SOFTI2C_CONSTRUCTION(n_args, n_kw, all_args); + enum { ARG_id, ARG_scl, ARG_sda, ARG_freq, ARG_timeout }; static const mp_arg_t allowed_args[] = { { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, From 98182a97c5a9229938406beb722966eacceeb823 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 29 Sep 2020 16:50:23 +1000 Subject: [PATCH 090/337] docs: Update I2C and SPI docs to add reference to SoftI2C and SoftSPI. Signed-off-by: Damien George --- docs/esp32/quickref.rst | 62 ++++++++++++++++++++++-------------- docs/esp8266/quickref.rst | 9 +++--- docs/library/machine.I2C.rst | 38 ++++++++++++++++------ docs/library/machine.SPI.rst | 20 ++++++++++-- 4 files changed, 89 insertions(+), 40 deletions(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index d5c222f3a1..79e61a10b6 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -249,16 +249,15 @@ ESP32 specific ADC class method reference: Software SPI bus ---------------- -There are two SPI drivers. One is implemented in software (bit-banging) -and works on all pins, and is accessed via the :ref:`machine.SPI ` -class:: +Software SPI (using bit-banging) works on all pins, and is accessed via the +:ref:`machine.SoftSPI ` class:: - from machine import Pin, SPI + from machine import Pin, SoftSPI - # construct an SPI bus on the given pins + # construct a SoftSPI bus on the given pins # polarity is the idle state of SCK # phase=0 means sample on the first edge of SCK, phase=1 means the second - spi = SPI(baudrate=100000, polarity=1, phase=0, sck=Pin(0), mosi=Pin(2), miso=Pin(4)) + spi = SoftSPI(baudrate=100000, polarity=1, phase=0, sck=Pin(0), mosi=Pin(2), miso=Pin(4)) spi.init(baudrate=200000) # set the baudrate @@ -298,39 +297,54 @@ mosi 13 23 miso 12 19 ===== =========== ============ -Hardware SPI has the same methods as Software SPI above:: +Hardware SPI is accessed via the :ref:`machine.SPI ` class and +has the same methods as software SPI above:: from machine import Pin, SPI hspi = SPI(1, 10000000, sck=Pin(14), mosi=Pin(13), miso=Pin(12)) vspi = SPI(2, baudrate=80000000, polarity=0, phase=0, bits=8, firstbit=0, sck=Pin(18), mosi=Pin(23), miso=Pin(19)) +Software I2C bus +---------------- -I2C bus -------- +Software I2C (using bit-banging) works on all output-capable pins, and is +accessed via the :ref:`machine.SoftI2C ` class:: -The I2C driver has both software and hardware implementations, and the two -hardware peripherals have identifiers 0 and 1. Any available output-capable -pins can be used for SCL and SDA. The driver is accessed via the -:ref:`machine.I2C ` class:: + from machine import Pin, SoftI2C - from machine import Pin, I2C + i2c = SoftI2C(scl=Pin(5), sda=Pin(4), freq=100000) - # construct a software I2C bus - i2c = I2C(scl=Pin(5), sda=Pin(4), freq=100000) + i2c.scan() # scan for devices - # construct a hardware I2C bus - i2c = I2C(0) - i2c = I2C(1, scl=Pin(5), sda=Pin(4), freq=400000) - - i2c.scan() # scan for slave devices - - i2c.readfrom(0x3a, 4) # read 4 bytes from slave device with address 0x3a - i2c.writeto(0x3a, '12') # write '12' to slave device with address 0x3a + i2c.readfrom(0x3a, 4) # read 4 bytes from device with address 0x3a + i2c.writeto(0x3a, '12') # write '12' to device with address 0x3a buf = bytearray(10) # create a buffer with 10 bytes i2c.writeto(0x3a, buf) # write the given buffer to the slave +Hardware I2C bus +---------------- + +There are two hardware I2C peripherals with identifiers 0 and 1. Any available +output-capable pins can be used for SCL and SDA but the defaults are given +below. + +===== =========== ============ +\ I2C(0) I2C(1) +===== =========== ============ +scl 18 25 +sda 19 26 +===== =========== ============ + +The driver is accessed via the :ref:`machine.I2C ` class and +has the same methods as software I2C above:: + + from machine import Pin, I2C + + i2c = I2C(0) + i2c = I2C(1, scl=Pin(5), sda=Pin(4), freq=400000) + Real time clock (RTC) --------------------- diff --git a/docs/esp8266/quickref.rst b/docs/esp8266/quickref.rst index c884d93fc9..1a22bd50a5 100644 --- a/docs/esp8266/quickref.rst +++ b/docs/esp8266/quickref.rst @@ -214,15 +214,15 @@ Software SPI bus ---------------- There are two SPI drivers. One is implemented in software (bit-banging) -and works on all pins, and is accessed via the :ref:`machine.SPI ` +and works on all pins, and is accessed via the :ref:`machine.SoftSPI ` class:: - from machine import Pin, SPI + from machine import Pin, SoftSPI # construct an SPI bus on the given pins # polarity is the idle state of SCK # phase=0 means sample on the first edge of SCK, phase=1 means the second - spi = SPI(-1, baudrate=100000, polarity=1, phase=0, sck=Pin(0), mosi=Pin(2), miso=Pin(4)) + spi = SoftSPI(baudrate=100000, polarity=1, phase=0, sck=Pin(0), mosi=Pin(2), miso=Pin(4)) spi.init(baudrate=200000) # set the baudrate @@ -258,7 +258,8 @@ I2C bus ------- The I2C driver is implemented in software and works on all pins, -and is accessed via the :ref:`machine.I2C ` class:: +and is accessed via the :ref:`machine.I2C ` class (which is an +alias of :ref:`machine.SoftI2C `):: from machine import Pin, I2C diff --git a/docs/library/machine.I2C.rst b/docs/library/machine.I2C.rst index 743554bc76..f9b9515646 100644 --- a/docs/library/machine.I2C.rst +++ b/docs/library/machine.I2C.rst @@ -12,6 +12,14 @@ when created, or initialised later on. Printing the I2C object gives you information about its configuration. +Both hardware and software I2C implementations exist via the +:ref:`machine.I2C ` and `machine.SoftI2C` classes. Hardware I2C uses +underlying hardware support of the system to perform the reads/writes and is +usually efficient and fast but may have restrictions on which pins can be used. +Software I2C is implemented by bit-banging and can be used on any pin but is not +as efficient. These classes have the same methods available and differ primarily +in the way they are constructed. + Example usage:: from machine import I2C @@ -33,22 +41,34 @@ Example usage:: Constructors ------------ -.. class:: I2C(id=-1, *, scl, sda, freq=400000) +.. class:: I2C(id, *, scl, sda, freq=400000) Construct and return a new I2C object using the following parameters: - - *id* identifies a particular I2C peripheral. The default - value of -1 selects a software implementation of I2C which can - work (in most cases) with arbitrary pins for SCL and SDA. - If *id* is -1 then *scl* and *sda* must be specified. Other - allowed values for *id* depend on the particular port/board, - and specifying *scl* and *sda* may or may not be required or - allowed in this case. + - *id* identifies a particular I2C peripheral. Allowed values for + depend on the particular port/board - *scl* should be a pin object specifying the pin to use for SCL. - *sda* should be a pin object specifying the pin to use for SDA. - *freq* should be an integer which sets the maximum frequency for SCL. + Note that some ports/boards will have default values of *scl* and *sda* + that can be changed in this constructor. Others will have fixed values + of *scl* and *sda* that cannot be changed. + +.. _machine.SoftI2C: +.. class:: SoftI2C(scl, sda, *, freq=400000, timeout=255) + + Construct a new software I2C object. The parameters are: + + - *scl* should be a pin object specifying the pin to use for SCL. + - *sda* should be a pin object specifying the pin to use for SDA. + - *freq* should be an integer which sets the maximum frequency + for SCL. + - *timeout* is the maximum time in microseconds to wait for clock + stretching (SCL held low by another device on the bus), after + which an ``OSError(ETIMEDOUT)`` exception is raised. + General Methods --------------- @@ -79,7 +99,7 @@ The following methods implement the primitive I2C master bus operations and can be combined to make any I2C transaction. They are provided if you need more control over the bus, otherwise the standard methods (see below) can be used. -These methods are available on software I2C only. +These methods are only available on the `machine.SoftI2C` class. .. method:: I2C.start() diff --git a/docs/library/machine.SPI.rst b/docs/library/machine.SPI.rst index ea460a7b81..7565241eb1 100644 --- a/docs/library/machine.SPI.rst +++ b/docs/library/machine.SPI.rst @@ -11,21 +11,35 @@ SS (Slave Select), to select a particular device on a bus with which communication takes place. Management of an SS signal should happen in user code (via machine.Pin class). +Both hardware and software SPI implementations exist via the +:ref:`machine.SPI ` and `machine.SoftSPI` classes. Hardware SPI uses underlying +hardware support of the system to perform the reads/writes and is usually +efficient and fast but may have restrictions on which pins can be used. +Software SPI is implemented by bit-banging and can be used on any pin but +is not as efficient. These classes have the same methods available and +differ primarily in the way they are constructed. + Constructors ------------ .. class:: SPI(id, ...) - Construct an SPI object on the given bus, ``id``. Values of ``id`` depend + Construct an SPI object on the given bus, *id*. Values of *id* depend on a particular port and its hardware. Values 0, 1, etc. are commonly used - to select hardware SPI block #0, #1, etc. Value -1 can be used for - bitbanging (software) implementation of SPI (if supported by a port). + to select hardware SPI block #0, #1, etc. With no additional parameters, the SPI object is created but not initialised (it has the settings from the last initialisation of the bus, if any). If extra arguments are given, the bus is initialised. See ``init`` for parameters of initialisation. +.. _machine.SoftSPI: +.. class:: SoftSPI(baudrate=500000, *, polarity=0, phase=0, bits=8, firstbit=MSB, sck=None, mosi=None, miso=None) + + Construct a new software SPI object. Additional parameters must be + given, usually at least *sck*, *mosi* and *miso*, and these are used + to initialise the bus. See `SPI.init` for a description of the parameters. + Methods ------- From 905a18aafefbe04aa2beceb84885c29aa156b975 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 24 Sep 2020 14:37:01 +1000 Subject: [PATCH 091/337] unix,windows: Implement mp_hal_time_ns using gettimeofday. This provides microsecond accuracy. Signed-off-by: Damien George --- ports/unix/unix_mphal.c | 5 +++-- ports/windows/windows_mphal.c | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ports/unix/unix_mphal.c b/ports/unix/unix_mphal.c index f43067f646..3fe2fa9fec 100644 --- a/ports/unix/unix_mphal.c +++ b/ports/unix/unix_mphal.c @@ -216,6 +216,7 @@ mp_uint_t mp_hal_ticks_us(void) { } uint64_t mp_hal_time_ns(void) { - time_t now = time(NULL); - return (uint64_t)now * 1000000000ULL; + struct timeval tv; + gettimeofday(&tv, NULL); + return (uint64_t)tv.tv_sec * 1000000000ULL + (uint64_t)tv.tv_usec * 1000ULL; } diff --git a/ports/windows/windows_mphal.c b/ports/windows/windows_mphal.c index 8ed087358f..b442b59147 100644 --- a/ports/windows/windows_mphal.c +++ b/ports/windows/windows_mphal.c @@ -255,3 +255,9 @@ mp_uint_t mp_hal_ticks_cpu(void) { return value.LowPart; #endif } + +uint64_t mp_hal_time_ns(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + return (uint64_t)tv.tv_sec * 1000000000ULL + (uint64_t)tv.tv_usec * 1000ULL; +} From d4b61b00172ccc231307e3ef33f66f28cb6b051f Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 24 Sep 2020 12:37:02 +1000 Subject: [PATCH 092/337] extmod/utime_mphal: Add generic utime.time_ns() function. It requires mp_hal_time_ns() to be provided by a port. This function allows very accurate absolute timestamps. Enabled on unix, windows, stm32, esp8266 and esp32. Signed-off-by: Damien George --- docs/library/utime.rst | 10 ++++++++-- extmod/utime_mphal.c | 6 ++++++ extmod/utime_mphal.h | 1 + ports/esp32/modutime.c | 1 + ports/esp8266/modutime.c | 1 + ports/stm32/modutime.c | 1 + ports/unix/modtime.c | 1 + tests/extmod/utime_time_ns.py | 24 ++++++++++++++++++++++++ tests/extmod/utime_time_ns.py.exp | 2 ++ 9 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 tests/extmod/utime_time_ns.py create mode 100644 tests/extmod/utime_time_ns.py.exp diff --git a/docs/library/utime.rst b/docs/library/utime.rst index 8c222ede5f..86fd27b3a5 100644 --- a/docs/library/utime.rst +++ b/docs/library/utime.rst @@ -216,8 +216,9 @@ Functions function returns number of seconds since a port-specific reference point in time (for embedded boards without a battery-backed RTC, usually since power up or reset). If you want to develop portable MicroPython application, you should not rely on this function - to provide higher than second precision. If you need higher precision, use - `ticks_ms()` and `ticks_us()` functions, if you need calendar time, + to provide higher than second precision. If you need higher precision, absolute + timestamps, use `time_ns()`. If relative times are acceptable then use the + `ticks_ms()` and `ticks_us()` functions. If you need calendar time, `gmtime()` or `localtime()` without an argument is a better choice. .. admonition:: Difference to CPython @@ -233,3 +234,8 @@ Functions hardware also lacks battery-powered RTC, so returns number of seconds since last power-up or from other relative, hardware-specific point (e.g. reset). + +.. function:: time_ns() + + Similar to `time()` but returns nanoseconds since the Epoch, as an integer (usually + a big integer, so will allocate on the heap). diff --git a/extmod/utime_mphal.c b/extmod/utime_mphal.c index 6aff2cac72..d053cf128b 100644 --- a/extmod/utime_mphal.c +++ b/extmod/utime_mphal.c @@ -99,4 +99,10 @@ STATIC mp_obj_t time_ticks_add(mp_obj_t ticks_in, mp_obj_t delta_in) { } MP_DEFINE_CONST_FUN_OBJ_2(mp_utime_ticks_add_obj, time_ticks_add); +// Returns the number of nanoseconds since the Epoch, as an integer. +STATIC mp_obj_t time_time_ns(void) { + return mp_obj_new_int_from_ull(mp_hal_time_ns()); +} +MP_DEFINE_CONST_FUN_OBJ_0(mp_utime_time_ns_obj, time_time_ns); + #endif // MICROPY_PY_UTIME_MP_HAL diff --git a/extmod/utime_mphal.h b/extmod/utime_mphal.h index 88a9ed4d37..57fc348832 100644 --- a/extmod/utime_mphal.h +++ b/extmod/utime_mphal.h @@ -37,5 +37,6 @@ MP_DECLARE_CONST_FUN_OBJ_0(mp_utime_ticks_us_obj); MP_DECLARE_CONST_FUN_OBJ_0(mp_utime_ticks_cpu_obj); MP_DECLARE_CONST_FUN_OBJ_2(mp_utime_ticks_diff_obj); MP_DECLARE_CONST_FUN_OBJ_2(mp_utime_ticks_add_obj); +MP_DECLARE_CONST_FUN_OBJ_0(mp_utime_time_ns_obj); #endif // MICROPY_INCLUDED_EXTMOD_UTIME_MPHAL_H diff --git a/ports/esp32/modutime.c b/ports/esp32/modutime.c index ac3edb4e9b..cf7178e0b1 100644 --- a/ports/esp32/modutime.c +++ b/ports/esp32/modutime.c @@ -97,6 +97,7 @@ STATIC const mp_rom_map_elem_t time_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_ticks_cpu), MP_ROM_PTR(&mp_utime_ticks_cpu_obj) }, { MP_ROM_QSTR(MP_QSTR_ticks_add), MP_ROM_PTR(&mp_utime_ticks_add_obj) }, { MP_ROM_QSTR(MP_QSTR_ticks_diff), MP_ROM_PTR(&mp_utime_ticks_diff_obj) }, + { MP_ROM_QSTR(MP_QSTR_time_ns), MP_ROM_PTR(&mp_utime_time_ns_obj) }, }; STATIC MP_DEFINE_CONST_DICT(time_module_globals, time_module_globals_table); diff --git a/ports/esp8266/modutime.c b/ports/esp8266/modutime.c index 86534722c7..bcfbf7baf2 100644 --- a/ports/esp8266/modutime.c +++ b/ports/esp8266/modutime.c @@ -120,6 +120,7 @@ STATIC const mp_rom_map_elem_t time_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_ticks_add), MP_ROM_PTR(&mp_utime_ticks_add_obj) }, { MP_ROM_QSTR(MP_QSTR_ticks_diff), MP_ROM_PTR(&mp_utime_ticks_diff_obj) }, { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&time_time_obj) }, + { MP_ROM_QSTR(MP_QSTR_time_ns), MP_ROM_PTR(&mp_utime_time_ns_obj) }, }; STATIC MP_DEFINE_CONST_DICT(time_module_globals, time_module_globals_table); diff --git a/ports/stm32/modutime.c b/ports/stm32/modutime.c index 2a37a130df..4bce45eb33 100644 --- a/ports/stm32/modutime.c +++ b/ports/stm32/modutime.c @@ -144,6 +144,7 @@ STATIC const mp_rom_map_elem_t time_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_ticks_cpu), MP_ROM_PTR(&mp_utime_ticks_cpu_obj) }, { MP_ROM_QSTR(MP_QSTR_ticks_add), MP_ROM_PTR(&mp_utime_ticks_add_obj) }, { MP_ROM_QSTR(MP_QSTR_ticks_diff), MP_ROM_PTR(&mp_utime_ticks_diff_obj) }, + { MP_ROM_QSTR(MP_QSTR_time_ns), MP_ROM_PTR(&mp_utime_time_ns_obj) }, }; STATIC MP_DEFINE_CONST_DICT(time_module_globals, time_module_globals_table); diff --git a/ports/unix/modtime.c b/ports/unix/modtime.c index 5343e479c7..e08409e20e 100644 --- a/ports/unix/modtime.c +++ b/ports/unix/modtime.c @@ -219,6 +219,7 @@ STATIC const mp_rom_map_elem_t mp_module_time_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_ticks_cpu), MP_ROM_PTR(&mp_utime_ticks_cpu_obj) }, { MP_ROM_QSTR(MP_QSTR_ticks_add), MP_ROM_PTR(&mp_utime_ticks_add_obj) }, { MP_ROM_QSTR(MP_QSTR_ticks_diff), MP_ROM_PTR(&mp_utime_ticks_diff_obj) }, + { MP_ROM_QSTR(MP_QSTR_time_ns), MP_ROM_PTR(&mp_utime_time_ns_obj) }, { MP_ROM_QSTR(MP_QSTR_gmtime), MP_ROM_PTR(&mod_time_gmtime_obj) }, { MP_ROM_QSTR(MP_QSTR_localtime), MP_ROM_PTR(&mod_time_localtime_obj) }, { MP_ROM_QSTR(MP_QSTR_mktime), MP_ROM_PTR(&mod_time_mktime_obj) }, diff --git a/tests/extmod/utime_time_ns.py b/tests/extmod/utime_time_ns.py new file mode 100644 index 0000000000..8f3890f1cb --- /dev/null +++ b/tests/extmod/utime_time_ns.py @@ -0,0 +1,24 @@ +# test utime.time_ns() + +try: + import utime + + utime.sleep_us + utime.time_ns +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + + +t0 = utime.time_ns() +utime.sleep_us(1000) +t1 = utime.time_ns() + +# Check that time_ns increases. +print(t0 < t1) + +# Check that time_ns counts correctly, but be very lenient with the upper bound (50ms). +if 950000 < t1 - t0 < 50000000: + print(True) +else: + print(t0, t1, t1 - t0) diff --git a/tests/extmod/utime_time_ns.py.exp b/tests/extmod/utime_time_ns.py.exp new file mode 100644 index 0000000000..dbde422651 --- /dev/null +++ b/tests/extmod/utime_time_ns.py.exp @@ -0,0 +1,2 @@ +True +True From 843dcd4f8515c62b32b4b25dc3b01fba107622e2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 30 Sep 2020 23:34:42 +1000 Subject: [PATCH 093/337] py/parse: Expose rule-name printing as MICROPY_DEBUG_PARSE_RULE_NAME. So it can be enabled without modifying the source. Signed-off-by: Damien George --- py/mpconfig.h | 5 +++++ py/parse.c | 7 ++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/py/mpconfig.h b/py/mpconfig.h index 12517316c6..cc83f3850d 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -454,6 +454,11 @@ #define MICROPY_DEBUG_MP_OBJ_SENTINELS (0) #endif +// Whether to print parse rule names (rather than integers) in mp_parse_node_print +#ifndef MICROPY_DEBUG_PARSE_RULE_NAME +#define MICROPY_DEBUG_PARSE_RULE_NAME (0) +#endif + // Whether to enable a simple VM stack overflow check #ifndef MICROPY_DEBUG_VM_STACK_OVERFLOW #define MICROPY_DEBUG_VM_STACK_OVERFLOW (0) diff --git a/py/parse.c b/py/parse.c index 38cd215a9f..da2f5e796d 100644 --- a/py/parse.c +++ b/py/parse.c @@ -55,9 +55,6 @@ #define RULE_ARG_RULE (0x2000) #define RULE_ARG_OPT_RULE (0x3000) -// (un)comment to use rule names; for debugging -// #define USE_RULE_NAME (1) - // *FORMAT-OFF* enum { @@ -192,7 +189,7 @@ static const size_t FIRST_RULE_WITH_OFFSET_ABOVE_255 = #undef DEF_RULE_NC 0; -#if USE_RULE_NAME +#if MICROPY_DEBUG_PARSE_RULE_NAME // Define an array of rule names corresponding to each rule STATIC const char *const rule_name_table[] = { #define DEF_RULE(rule, comp, kind, ...) #rule, @@ -410,7 +407,7 @@ void mp_parse_node_print(const mp_print_t *print, mp_parse_node_t pn, size_t ind #endif } else { size_t n = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); - #if USE_RULE_NAME + #if MICROPY_DEBUG_PARSE_RULE_NAME mp_printf(print, "%s(%u) (n=%u)\n", rule_name_table[MP_PARSE_NODE_STRUCT_KIND(pns)], (uint)MP_PARSE_NODE_STRUCT_KIND(pns), (uint)n); #else mp_printf(print, "rule(%u) (n=%u)\n", (uint)MP_PARSE_NODE_STRUCT_KIND(pns), (uint)n); From 817b80a102a413b6458ea391bb2c463aff99672e Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 30 Sep 2020 23:35:55 +1000 Subject: [PATCH 094/337] unix/variants: Enable MICROPY_DEBUG_PARSE_RULE_NAME on coverage build. Signed-off-by: Damien George --- .../unix/variants/coverage/mpconfigvariant.h | 1 + tests/cmdline/cmd_parsetree.py.exp | 22 +++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/ports/unix/variants/coverage/mpconfigvariant.h b/ports/unix/variants/coverage/mpconfigvariant.h index 3de5294322..942117608f 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.h +++ b/ports/unix/variants/coverage/mpconfigvariant.h @@ -30,6 +30,7 @@ #define MICROPY_VFS (1) #define MICROPY_PY_UOS_VFS (1) +#define MICROPY_DEBUG_PARSE_RULE_NAME (1) #define MICROPY_OPT_MATH_FACTORIAL (1) #define MICROPY_FLOAT_HIGH_QUALITY_HASH (1) #define MICROPY_ENABLE_SCHEDULER (1) diff --git a/tests/cmdline/cmd_parsetree.py.exp b/tests/cmdline/cmd_parsetree.py.exp index 42a8228fb7..e64f4f7829 100644 --- a/tests/cmdline/cmd_parsetree.py.exp +++ b/tests/cmdline/cmd_parsetree.py.exp @@ -1,31 +1,31 @@ ---------------- -[ 4] rule(1) (n=9) +[ 4] \(rule\|file_input_2\)(1) (n=9) tok(4) -[ 4] rule(22) (n=4) +[ 4] \(rule\|for_stmt\)(22) (n=4) id(i) -[ 4] rule(45) (n=1) +[ 4] \(rule\|atom_paren\)(45) (n=1) NULL -[ 5] rule(8) (n=0) +[ 5] \(rule\|pass_stmt\)(8) (n=0) NULL -[ 6] rule(5) (n=2) +[ 6] \(rule\|expr_stmt\)(5) (n=2) id(a) tok(14) -[ 7] rule(5) (n=2) +[ 7] \(rule\|expr_stmt\)(5) (n=2) id(b) str(str) -[ 8] rule(5) (n=2) +[ 8] \(rule\|expr_stmt\)(5) (n=2) id(c) [ 8] literal \.\+ -[ 9] rule(5) (n=2) +[ 9] \(rule\|expr_stmt\)(5) (n=2) id(d) bytes(bytes) -[ 10] rule(5) (n=2) +[ 10] \(rule\|expr_stmt\)(5) (n=2) id(e) [ 10] literal \.\+ -[ 11] rule(5) (n=2) +[ 11] \(rule\|expr_stmt\)(5) (n=2) id(f) [ 11] literal \.\+ -[ 12] rule(5) (n=2) +[ 12] \(rule\|expr_stmt\)(5) (n=2) id(g) int(123) ---------------- From 0fff2e03fe07471997a6df6f92c6960cfd225dc0 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 2 Oct 2020 15:46:13 +1000 Subject: [PATCH 095/337] stm32/Makefile: Allow boards to extend SRC_C, SRC_O and OBJ variables. Signed-off-by: Damien George --- ports/stm32/Makefile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 7b68299e04..cf3a589ca9 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -259,7 +259,7 @@ DRIVERS_SRC_C = $(addprefix drivers/,\ dht/dht.c \ ) -SRC_C = \ +SRC_C += \ main.c \ stm32_it.c \ usbd_conf.c \ @@ -330,7 +330,7 @@ SRC_C = \ adc.c \ $(wildcard $(BOARD_DIR)/*.c) -SRC_O = \ +SRC_O += \ $(STARTUP_FILE) \ $(SYSTEM_FILE) @@ -505,7 +505,6 @@ endif endif -OBJ = OBJ += $(PY_O) OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o)) OBJ += $(LIBM_O) From 1dc64359dab733b3c77f07d9d79589010f6fd8e0 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 23 Sep 2020 15:54:58 +1000 Subject: [PATCH 096/337] esp32: Use path relative to root for netutils/timeutils headers. Signed-off-by: Damien George --- ports/esp32/Makefile | 3 --- ports/esp32/fatfs_port.c | 2 +- ports/esp32/machine_rtc.c | 2 +- ports/esp32/modesp32.c | 2 +- ports/esp32/modnetwork.c | 2 +- ports/esp32/network_ppp.c | 2 +- 6 files changed, 5 insertions(+), 8 deletions(-) diff --git a/ports/esp32/Makefile b/ports/esp32/Makefile index dcf4110cfc..be28257722 100644 --- a/ports/esp32/Makefile +++ b/ports/esp32/Makefile @@ -136,9 +136,6 @@ include $(SDKCONFIG) INC += -I. INC += -I$(TOP) -INC += -I$(TOP)/lib/mp-readline -INC += -I$(TOP)/lib/netutils -INC += -I$(TOP)/lib/timeutils INC += -I$(BUILD) INC_ESPCOMP += -I$(ESPCOMP)/bootloader_support/include diff --git a/ports/esp32/fatfs_port.c b/ports/esp32/fatfs_port.c index cfc52853d0..7fce654c09 100644 --- a/ports/esp32/fatfs_port.c +++ b/ports/esp32/fatfs_port.c @@ -28,7 +28,7 @@ #include #include "lib/oofatfs/ff.h" -#include "timeutils.h" +#include "lib/timeutils/timeutils.h" DWORD get_fattime(void) { struct timeval tv; diff --git a/ports/esp32/machine_rtc.c b/ports/esp32/machine_rtc.c index 6902ce85fd..1b6a71b5b4 100644 --- a/ports/esp32/machine_rtc.c +++ b/ports/esp32/machine_rtc.c @@ -36,7 +36,7 @@ #include "py/obj.h" #include "py/runtime.h" #include "py/mphal.h" -#include "timeutils.h" +#include "lib/timeutils/timeutils.h" #include "modmachine.h" #include "machine_rtc.h" diff --git a/ports/esp32/modesp32.c b/ports/esp32/modesp32.c index 8e248370ff..28d1762d24 100644 --- a/ports/esp32/modesp32.c +++ b/ports/esp32/modesp32.c @@ -41,7 +41,7 @@ #include "py/obj.h" #include "py/runtime.h" #include "py/mphal.h" -#include "timeutils.h" +#include "lib/timeutils/timeutils.h" #include "modmachine.h" #include "machine_rtc.h" #include "modesp32.h" diff --git a/ports/esp32/modnetwork.c b/ports/esp32/modnetwork.c index 029a8d1431..4426335040 100644 --- a/ports/esp32/modnetwork.c +++ b/ports/esp32/modnetwork.c @@ -40,7 +40,7 @@ #include "py/runtime.h" #include "py/mphal.h" #include "py/mperrno.h" -#include "netutils.h" +#include "lib/netutils/netutils.h" #include "esp_eth.h" #include "esp_wifi.h" #include "esp_log.h" diff --git a/ports/esp32/network_ppp.c b/ports/esp32/network_ppp.c index df57b81725..db2292ca03 100644 --- a/ports/esp32/network_ppp.c +++ b/ports/esp32/network_ppp.c @@ -30,7 +30,7 @@ #include "py/mphal.h" #include "py/objtype.h" #include "py/stream.h" -#include "netutils.h" +#include "lib/netutils/netutils.h" #include "modmachine.h" #include "netif/ppp/ppp.h" From 7497d891a7cfade68d8c7d26b64080c56e0579e5 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Fri, 2 Oct 2020 21:23:09 +0200 Subject: [PATCH 097/337] 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 79cc9c9a32..87f550e3a6 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 098/337] 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 9d77722302..7d79e9f47b 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 099/337] 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 418b8bde2a..3f4f33979a 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 dc23bbe0b2..436042cc2e 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 fbe111e1eb..39f6220a32 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 100/337] 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 cfe315605e..4dbead0acc 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 @@ sram2a_dump(264) 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 0000000000..53c96a552a --- /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 0000000000..23f3d20f0c --- /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 101/337] 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 436042cc2e..3850a17dae 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 102/337] 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 676c364da7..54f4b0204b 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 31542a7f9b..6a040b7773 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 4fa59f4634..4e51f259e7 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 103/337] 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 40900dc050..7f75232941 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 f80ded678b..508ae5e2e5 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 104/337] 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 53c96a552a..de98d97430 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 105/337] 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 de98d97430..3358b246d0 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 106/337] 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 3358b246d0..b5f1d0072e 100644 --- a/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py +++ b/ports/stm32/boards/NUCLEO_WB55/rfcore_firmware.py @@ -180,6 +180,12 @@ _PATH_WS_BLE_HCI = "ws_ble_hci.bin" _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 3850a17dae..1fc0c9531d 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 39f6220a32..6a3c85f67d 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 107/337] 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 70f8ef527c..f6b09a362d 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 3704e5d0dd..f6724c1bc0 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 019e3a891c..f3d417f1a1 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 108/337] 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 0036eac25d..aa8aecedfc 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 109/337] 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 de52ef7e7e..ba91085210 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 110/337] 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 b580353d6b..18dc6f2afa 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 111/337] 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 651c8c8497..1e1fcb5483 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 112/337] 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 2d1287f325..9991d3aebc 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 113/337] 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 c74d2fd84a..17f4895573 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 114/337] 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 5bf0676b23..b63d1f8955 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 115/337] 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 7d79e9f47b..b255ee82ca 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 0fefcbab90..c8805d6829 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 116/337] 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 7a09128527..a8261a958a 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 117/337] 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 d63f372298..27a66aa76c 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 118/337] 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 073c2a38c2..98e02fb53f 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 b52d98ea1c..edf164c4ad 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 119/337] 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 517427ed71..885032c3e3 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 120/337] 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 2853da26c5..cffaa4bb89 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 6cc2e4d34b..5dac2ae390 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 121/337] 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 9203f16f6d..12c9abbcba 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 9cf3eb1108..dd78269a46 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 b7948f39e7..3ab3ff334c 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 122/337] 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 068214bf91..7912270872 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 123/337] 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 e312acba86..b000b058d7 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 124/337] 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 811258424a..c5fbf12e42 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 3f54b3993f..c4d12af87a 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 bb337decfb..f8cb926292 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 9c6336852a..767f1b81c4 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 7d2a251d41..07ea2b96ab 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 cc83f3850d..854188b66b 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 f31a901a70..dac9b11382 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 125/337] 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 f80ee761b7..971f2f81aa 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 126/337] 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 ff5f355022..7380e5e412 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 127/337] 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 c5a402b3a3..8be7f2d737 100644 --- a/tests/thread/stress_schedule.py +++ b/tests/thread/stress_schedule.py @@ -14,7 +14,11 @@ except AttributeError: 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 128/337] 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 9cb7761ac2..faceda2f4f 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 5c97e2c44b..9f43da4030 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 ed524fecca..b100daaa98 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 8dbc9ecea7..83d07ad5b1 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 f29896d37c..9c20f0f954 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 129/337] 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 35d5f03c59..f865e46060 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 2c40b29897..3d8cec6075 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 b5c5215314..aa654ebe05 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 130/337] 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 b63d1f8955..f170d70708 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 131/337] 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 974310f84d..99cf2ade4d 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 132/337] 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 7ea41bb6f4..5f8e7ec2de 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 133/337] 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 b23941998a..eea02f7265 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 134/337] 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 ab83b0f701..5a736c1ebc 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 0000000000..a032b9362b --- /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 135/337] 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 9449a46ee9..47afe0adb9 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 136/337] 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 47afe0adb9..1db00d9633 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 137/337] 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 be28257722..94374eb1c4 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 c37c25db4b..f003174ea5 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 138/337] 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 1db00d9633..f514ae0c10 100644 --- a/py/makeqstrdefs.py +++ b/py/makeqstrdefs.py @@ -8,6 +8,7 @@ This script works with Python 2.6, 2.7, 3.3 and 3.4. from __future__ import print_function import re +import subprocess import sys import io import os @@ -20,6 +21,31 @@ _MODE_QSTR = "qstr" _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 @@ if __name__ == "__main__": 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 f003174ea5..3bfe64d753 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 139/337] 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 2dd20365c9..aac9072446 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 140/337] 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 1b3b9f903b..3550a8bd42 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 141/337] 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 e616adad03..849d0e60a2 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 d864a7ed3d..bac38f0741 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 142/337] 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 c9fcc21336..3b399804e3 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 94374eb1c4..756bc8f894 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 7380e5e412..3388d67a2d 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 0000000000..ea7418e1dd --- /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 0fe492a554..6f85cbf8d0 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 36105f6bad..1c028506e3 100644 --- a/tests/unix/extra_coverage.py +++ b/tests/unix/extra_coverage.py @@ -46,6 +46,9 @@ stream.set_error(uerrno.EAGAIN) 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 7d7b7dd9f9..514ff9437b 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 143/337] 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 | 186 +++++++++--------- 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, 205 insertions(+), 99 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 849d0e60a2..2d08df8282 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 - - -``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); - - // 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 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); - - // 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_example, example_user_cmodule, MODULE_EXAMPLE_ENABLED); - - -``micropython.mk`` - -.. code-block:: make - - EXAMPLE_MOD_DIR := $(USERMOD_DIR) - - # 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) - -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 - - #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. +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. Compiling the cmodule into MicroPython -------------------------------------- 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: +`_), +applying 2 modifications: + +- 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:: + + my_project/ + ├── modules/ + │ └──example1/ + │ ├──example1.c + │ └──micropython.mk + │ └──example2/ + │ ├──example2.c + │ └──micropython.mk + └── micropython/ + ├──ports/ + ... ├──stm32/ + ... -Directory:: + with ``USER_C_MODULES`` set to the ``my_project/modules`` directory. - my_project/ - ├── modules/ - │ └──example/ - │ ├──example.c - │ └──micropython.mk - └── micropython/ - ├──ports/ - ... ├──stm32/ - ... +- 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 -Building for stm32 port: + .. code-block:: c + + MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule, MODULE_CEXAMPLE_ENABLED); + + + 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 + + .. code-block:: c + + #define MODULE_CEXAMPLE_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. + +To sum up, here's how the ``cexample`` module from the ``examples/usercmodule`` +directory can be built for the unix port: + +.. code-block:: bash + + 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:: + + ... + Including User C Module from ../../examples/usercmodule/cexample + Including User C Module from ../../examples/usercmodule/cppexample + ... + + +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 0000000000..f608823c9e --- /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 0000000000..dbfe3c5cbd --- /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 0000000000..06809732a4 --- /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 0000000000..ceb588bef6 --- /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 0000000000..d89384a630 --- /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 0000000000..e10d965a00 --- /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 f11d0b0d28..55399831aa 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 1c028506e3..b4808993a7 100644 --- a/tests/unix/extra_coverage.py +++ b/tests/unix/extra_coverage.py @@ -49,6 +49,16 @@ print(buf.write(bytearray(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 514ff9437b..257224108a 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 144/337] 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 2d08df8282..2db1f65f2e 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 145/337] 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 3388d67a2d..048ef97f77 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 55399831aa..ef81975d9d 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 146/337] 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 3b2e45d9b6..c9199b341b 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 1dd4c11ed9..5866f0b5cc 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 5f920b417f..b6957a3213 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 2e428aa62c..dbda1b8b68 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 52b2a677d7..21d809a3d2 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 65722f2e57..5517a2d09c 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 0000000000..1bb2249e96 --- /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 0000000000..e5f25a3c6f --- /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 0000000000..8230f8f9b2 --- /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 0000000000..526d2519f7 --- /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 74b2ffb419..733ca12f67 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 147/337] 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 3b399804e3..14595ddeb1 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 cf3a589ca9..61da9cc980 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 e5f25a3c6f..eb9b86f49d 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 8230f8f9b2..aba6bf57c8 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 526d2519f7..5a29e47307 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 b5dbfa50fd..f19dac0e79 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 148/337] 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 dae5b15186..9d2f24745f 100644 --- a/tests/micropython/extreme_exc.py +++ b/tests/micropython/extreme_exc.py @@ -126,7 +126,7 @@ def main(): ) 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 149/337] 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 7e5d2f646d..e3309b61db 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 150/337] 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 f8a68bc6f5..fbd031b3e3 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 151/337] 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 f19dac0e79..7f8ee84fd4 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 @@ soft_reset: 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 @@ soft_reset_exit: 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 152/337] 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 61da9cc980..fa9998a8e0 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 0000000000..188068d705 --- /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 0000000000..05bade305c --- /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 7f8ee84fd4..c749cac978 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 @@ soft_reset: // 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 @@ soft_reset: // 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 @@ soft_reset_exit: // 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 @@ soft_reset_exit: 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 153/337] 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 05bade305c..6f79dfb890 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 c749cac978..db8222479b 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 154/337] 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 2c8ca2de0c..2b86af3bba 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 155/337] 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 b3d8806792..7515930538 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 6e52073a6e..ef3e017967 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 380293f36c..24541c8a42 100644 --- a/tests/basics/struct1_intbig.py +++ b/tests/basics/struct1_intbig.py @@ -36,3 +36,11 @@ 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 156/337] 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 c5fbf12e42..79a49d5c32 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 0000000000..709b9f5c29 --- /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 0000000000..f6f7bc96ef --- /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 157/337] 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 d0f72ec23c..1847894b71 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 709b9f5c29..3b9bb6d730 100644 --- a/tests/extmod/uctypes_array_load_store.py +++ b/tests/extmod/uctypes_array_load_store.py @@ -6,7 +6,7 @@ except ImportError: 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 @@ for endian in ("NATIVE", "LITTLE_ENDIAN", "BIG_ENDIAN"): 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 f6f7bc96ef..10de804645 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 158/337] 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 f514ae0c10..ad4f22d5e4 100644 --- a/py/makeqstrdefs.py +++ b/py/makeqstrdefs.py @@ -7,11 +7,12 @@ This script works with Python 2.6, 2.7, 3.3 and 3.4. 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 159/337] 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 fa9998a8e0..8f7cdd93ca 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 160/337] 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 a55cc1f4ab..dcdd53be92 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 161/337] 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 a1494bd51a..73e2f7fd1f 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 a6eb8c01ed..5b462a3b15 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 6ff38cc051..0c7f8122f4 100644 --- a/tests/extmod/machine1.py +++ b/tests/extmod/machine1.py @@ -26,3 +26,23 @@ try: 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 bb421ea5cf..2504859690 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 162/337] 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 8f7cdd93ca..18f7e8a1a1 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 163/337] 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 faceda2f4f..5c6f31d1dd 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 cdae0c5629..8d8bb8f4ee 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 05ab10fea0..83a144f8fe 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 9f43da4030..231f1ac7f4 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 b100daaa98..6b5ece766a 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 cfffcffbbe..215e798b92 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 83d07ad5b1..91309e286f 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 18ecf77505..bd898d4558 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 164/337] 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 ad032df38b..5b8e70e921 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 165/337] 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 026b9d551c..0f22daff88 100644 --- a/tests/multi_bluetooth/ble_characteristic.py +++ b/tests/multi_bluetooth/ble_characteristic.py @@ -31,28 +31,31 @@ SERVICE = ( ) 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. + # A + + # Wait for a write to the characteristic from the central, + # then reply with a notification. wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS) - - # Wait a bit, then write the characteristic and notify it. - time.sleep_ms(1000) 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 90dc46c06f..31667415ee 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 bb6ff8e425..bb1ef94a71 100644 --- a/tests/multi_bluetooth/ble_gap_advertise.py +++ b/tests/multi_bluetooth/ble_gap_advertise.py @@ -46,7 +46,10 @@ def instance1(): 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 8b40a29163..2c1d2cbbc5 100644 --- a/tests/multi_bluetooth/ble_gap_connect.py +++ b/tests/multi_bluetooth/ble_gap_connect.py @@ -10,101 +10,77 @@ _IRQ_CENTRAL_DISCONNECT = const(2) _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 fbc9d80bae..e7202170bd 100644 --- a/tests/multi_bluetooth/ble_gap_device_name.py +++ b/tests/multi_bluetooth/ble_gap_device_name.py @@ -16,45 +16,44 @@ _IRQ_GATTC_READ_DONE = const(16) 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 0f4ee6e3dc..a7a7aa3670 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 944c9e2d2a..e8249b3da8 100644 --- a/tests/multi_bluetooth/ble_gatt_data_transfer.py +++ b/tests/multi_bluetooth/ble_gatt_data_transfer.py @@ -29,35 +29,33 @@ CHAR_TX = (CHAR_TX_UUID, bluetooth.FLAG_NOTIFY) 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 57f95bf012..00bb94bba0 100644 --- a/tests/multi_bluetooth/ble_gattc_discover_services.py +++ b/tests/multi_bluetooth/ble_gattc_discover_services.py @@ -24,45 +24,42 @@ SERVICE_B = ( ) 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 7f4d8da81a..d14f802377 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 ef3199c5bb..73c77e2460 100644 --- a/tests/multi_bluetooth/ble_mtu.py +++ b/tests/multi_bluetooth/ble_mtu.py @@ -51,7 +51,6 @@ waiting_events = {} 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 166/337] 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 5b8e70e921..cdb2730eda 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 167/337] 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 ae96035868..fd5d343637 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 57f69433a1..90ed495462 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 2df4c3c25f..618939ab18 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 c0d6d64cfc..acb4c03dc3 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 168/337] 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 17f4895573..d838f42b3f 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 169/337] 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 95ad63221e..89d23ca5d2 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 3fe2fa9fec..28a4ca2c4a 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 b442b59147..49daa05428 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 170/337] 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 fd5d343637..a4cc601746 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 90ed495462..6bad134959 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 618939ab18..dee7186a2d 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 acb4c03dc3..621278e0f1 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 171/337] 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 621278e0f1..b387e6ae1f 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 172/337] 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 a4cc601746..97dd2cbb53 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 6bad134959..d8068df594 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 dee7186a2d..6ed086d553 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 b387e6ae1f..7ee6ae8677 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 18f7e8a1a1..75090a077e 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 048ef97f77..f72c05f1ad 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 173/337] 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 | 36 ++--- 15 files changed, 280 insertions(+), 157 deletions(-) diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index 6ed086d553..52e3446ff3 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 c6d0850fea..230970b089 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 1ff1c17436..647e8ab477 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 7ee6ae8677..c961aee326 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 fbd031b3e3..00a244d6ea 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 2ec012940f..b68957fabf 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 (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) { + return; + } - 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). + // 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; - } - if (ev2->next == NULL) { + // Already in the list (e.g. a fragmented ACL will enqueue an + // event to process it for each fragment). break; } - DEBUG_EVENT_printf(" --> %p\n", ev2->next); - ev2 = ev2->next; + 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", 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 3ef07aa9cc..bfabe56e89 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 db8222479b..d00c2ec713 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 a5977ff12c..ee9f4e31eb 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 5f8e7ec2de..b6906ef998 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 1d7c095139..0ba76fb277 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 585f81e8bd..aa8f90e3e4 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 1fc0c9531d..08338fcdc2 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 316a8831fe..14afbebcd7 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 8961910098..29f558f74d 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) { - - // 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(); - - // Run pending background operations and events, but only after HCI sync. + // Run any timers. mp_bluetooth_nimble_os_callout_process(); - mp_bluetooth_nimble_os_eventq_run_all(); - MICROPY_END_ATOMIC_SECTION(atomic_state); + // 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(); } return true; } +bool mp_bluetooth_hci_active(void) { + return mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF; +} + // 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 -} - -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"); + // 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 174/337] 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 0f22daff88..327822a01e 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 2c1d2cbbc5..95d1be7baa 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 e7202170bd..556068114b 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 e8249b3da8..861cb49fc3 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 00bb94bba0..f6ee5fa00e 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 73c77e2460..f202ec764d 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 175/337] 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 0000000000..83df555ce4 --- /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 0000000000..8e1144b787 --- /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 176/337] 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 08338fcdc2..550e5323e8 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 177/337] 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 499129a6f3..d399ece866 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 c901dfb334..3d041e1c14 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 127696b193..a73a26b162 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 550e5323e8..6133720958 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 6a3c85f67d..fe29ac612c 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 178/337] 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 6133720958..da77be6117 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 179/337] 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 da77be6117..7d0a8520a6 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 180/337] 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 9995d6bc07..426a47a6b4 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 79576e9d62..e711a57d4b 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 181/337] 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 b08a034561..5fceb6705f 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 182/337] 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 12c9abbcba..44161fbbb9 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 e3a87e282a..9e43747323 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 a1d0ad0f4d..87b08fc9bc 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 ab4d516621..aac9320873 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 e0c408c6d0..cfa00b86d8 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 ec4b8620a5..efd4bdcb21 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 183/337] 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 d8068df594..2cff386f12 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 52e3446ff3..9caddb0f3f 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 c961aee326..fa416ebc3f 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 7e401781e7..9ed64368b2 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 bfabe56e89..d0803f7e2e 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 8a6f2338be..bef6b3b921 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 75090a077e..ced851ca38 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 f72c05f1ad..6a936a2425 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 aac9072446..fe2b3b8afa 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 184/337] 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 f94ad3a612..35ac13ad56 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 185/337] 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 0000000000..a26f59b3ef --- /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 0000000000..0c572d99fe --- /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 186/337] 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 fa416ebc3f..bbff26b535 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 187/337] 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 0000000000..3bea99365d --- /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 0000000000..1b1f59b43e --- /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 188/337] 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 c550c1d952..31cf73bb58 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 189/337] 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 0000000000..3b906b449c --- /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 190/337] 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 0000000000..aab347d78e --- /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 0000000000..dd759a4f3f --- /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 0000000000..0eaa36da37 --- /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 0000000000..b89e462e04 --- /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 0000000000..f6f2fcaf4a --- /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 0000000000..1ba3b0ce61 --- /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 0000000000..88fa59767b --- /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 0000000000..f456dfd74a --- /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 0000000000..bde4ed965f --- /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 0000000000..4634339c96 --- /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 0000000000..7ae77c80af --- /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 0000000000..385b2c008a --- /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 0000000000..1bfe40c7fd --- /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 0000000000..d9ae2b8c55 --- /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 191/337] 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 14595ddeb1..0000000000 --- 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 192/337] 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 eebc8c4252..23c30d3c3c 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 193/337] 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 57daebf872..f75590a5ba 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 194/337] 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 f456dfd74a..8d144ca3cb 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 385b2c008a..552fd72d84 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 195/337] 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 2b86af3bba..86321ac129 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 @@ raw_repl_reset: 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 196/337] 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 c97ddbe487..3ecdf03f15 100755 --- a/tools/pyboard.py +++ b/tools/pyboard.py @@ -253,6 +253,7 @@ class ProcessPtyToTerminal: 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 @@ class Pyboard: # 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 @@ class Pyboard: 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 197/337] 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 06ba918117..55f76ee1a0 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 198/337] 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 0b15c9e079..e8822c0697 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 045b4cd139..6a84b0982c 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 6e1305c94f..d306752312 100644 --- a/extmod/uasyncio/funcs.py +++ b/extmod/uasyncio/funcs.py @@ -21,9 +21,9 @@ async def wait_for(aw, timeout, sleep=core.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 1788cf0ed0..2420ab7193 100644 --- a/extmod/uasyncio/task.py +++ b/extmod/uasyncio/task.py @@ -130,13 +130,16 @@ class Task: 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 @@ class Task: 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 @@ class Task: 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 ad62a79b7b..fe7b83eb4d 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 4744641e54..fb4711469d 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 199/337] 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 e8822c0697..fe0f748cac 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 2420ab7193..68ddf496f0 100644 --- a/extmod/uasyncio/task.py +++ b/extmod/uasyncio/task.py @@ -148,6 +148,9 @@ class Task: # 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 0000000000..2700da8c34 --- /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 0000000000..ddda04c5ec --- /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 200/337] 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 d306752312..93f4fd256c 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 92fd174b84..9612d16204 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 41a5f2481e..a4201d31ff 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 0000000000..33738085ce --- /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 0000000000..9f22f1a7d1 --- /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 23c30d3c3c..cb5b5cd0a5 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 201/337] 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 bbff26b535..e4a048e3d3 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 202/337] 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 97dd2cbb53..9faae26e46 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 2cff386f12..dc1084acc1 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 9caddb0f3f..2619e71894 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 e4a048e3d3..3983d18690 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 203/337] 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 35ac13ad56..73547c356a 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 204/337] 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 d2b134e714..0ebe431764 100644 --- a/examples/bluetooth/ble_simple_peripheral.py +++ b/examples/bluetooth/ble_simple_peripheral.py @@ -12,14 +12,19 @@ _IRQ_CENTRAL_CONNECT = const(1) _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 d375a62ffb..e6378ebee7 100644 --- a/examples/bluetooth/ble_temperature.py +++ b/examples/bluetooth/ble_temperature.py @@ -15,12 +15,16 @@ _IRQ_CENTRAL_CONNECT = const(1) _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 6d167a871a..b4e352be9f 100644 --- a/examples/bluetooth/ble_uart_peripheral.py +++ b/examples/bluetooth/ble_uart_peripheral.py @@ -9,14 +9,17 @@ _IRQ_CENTRAL_CONNECT = const(1) _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 9faae26e46..da9a6f732b 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 dc1084acc1..1e67c8ce32 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 2619e71894..0d4296626b 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 3983d18690..4484df937f 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 205/337] 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 73547c356a..434759b432 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 206/337] 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 1e67c8ce32..ed983b7946 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 0d4296626b..9856d4a689 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 4484df937f..e79dc2d4cb 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 207/337] 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 434759b432..f65020d2ae 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 208/337] 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 ed983b7946..b9b7c71248 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 9856d4a689..2cb2c709c5 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 00a244d6ea..ba094f16f8 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 209/337] 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 f65020d2ae..d402c6a9eb 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 da9a6f732b..825a9ab7b1 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 b9b7c71248..5d7de7b97f 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 2cb2c709c5..62ff6f2f9e 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 e79dc2d4cb..312a564263 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 210/337] 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 825a9ab7b1..ae4bac0094 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 5d7de7b97f..06e340c42d 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 62ff6f2f9e..977453becd 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 312a564263..852b9eac02 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 bef6b3b921..a051a8fefd 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 211/337] 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 d402c6a9eb..29d3044ebf 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 212/337] 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 ae4bac0094..f6af664a4e 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 06e340c42d..0d33130db7 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 977453becd..48e75d432e 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 852b9eac02..b1f13ee98b 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 213/337] 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 29d3044ebf..594d73c878 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 214/337] 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 0d33130db7..a69e8418a7 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 48e75d432e..d79b7f56b9 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 b1f13ee98b..2b273c6bc7 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 ba094f16f8..806630074e 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 215/337] 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 594d73c878..9ce400e7eb 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 216/337] 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 a69e8418a7..ba5333e70e 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 217/337] 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 ba5333e70e..caaf872e7d 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 d79b7f56b9..f14033de30 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 2b273c6bc7..ae727086ea 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 218/337] 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 0000000000..bd7596dbcb --- /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 219/337] 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 9ce400e7eb..3d435a7bed 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 220/337] 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 ae727086ea..613e325a95 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 221/337] 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 0000000000..728513405e --- /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 0000000000..9efe0fb22b --- /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 0000000000..a29c217887 --- /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 0000000000..271403a71a --- /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 222/337] 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 9fa00a9151..c5f49b6ef4 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 223/337] 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 ced851ca38..c3b7bbba98 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 224/337] 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 946185fba0..01ad183418 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 225/337] 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 01ad183418..7d36816979 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 226/337] 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 7d36816979..c36351e311 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 227/337] 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 c36351e311..ce2f6e1905 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 228/337] 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 ce2f6e1905..076215ba9f 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 229/337] 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 076215ba9f..bf8be647f4 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 230/337] 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 5981df11c5..d67201b714 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 4846f1cf3a..d494ad66d5 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 cfa00b86d8..41e65cf05c 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 4f8f7a0e37..ee8b498a13 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 231/337] 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 d67201b714..0b7105344c 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 2862f4c0e7..31e6a1789e 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 a73a26b162..8890abc59b 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 224b8f28b0..def66481cb 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 5f9b1c1a28..07374d7a38 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 9b8c14c0da..a34d2984da 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 232/337] 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 c3b7bbba98..a9dac03d2c 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 e5fc55e409..bcc6983594 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 3b906b449c..ded9fab922 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 233/337] 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 71b44d7df1..52449ea381 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 50cfa00502..9824f48e33 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 234/337] 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 efd4bdcb21..aa8823392d 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 29e6c889c4..968f758b93 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 235/337] 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 7169c7793b..ef66c4fb59 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 31cf73bb58..3218bd2f4d 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 84728e6f2d..9f8da873d6 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 b21df22a3b..ed79ad68a9 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 257224108a..d97de2c0ef 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 236/337] 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 f1f9a2a4c4..9c4c9c8bd1 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 237/337] 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 e5a1b471a3..4ee9384ec2 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 238/337] 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 4ee9384ec2..8680950804 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 239/337] 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 0000000000..c23a2dbda4 --- /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 240/337] 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 5fceb6705f..22bb4af514 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 c39a800d77..f993c7dc53 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 f98d5c88a9..ca437418d5 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 d6c6e743a4..0000000000 --- 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 ca555d3932..ca437418d5 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 241/337] 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 69738ab8b9..8cf8a4e591 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 242/337] stm32/eth: Add support for H7 processors. --- ports/stm32/eth.c | 229 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 225 insertions(+), 4 deletions(-) diff --git a/ports/stm32/eth.c b/ports/stm32/eth.c index 33d20707e2..7a08e8c28c 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 243/337] 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 4e6b162269..324f2b03ce 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 244/337] 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 | 1171 ++++++++++++++++++++++++++++------------ lib/littlefs/lfs2.h | 40 +- 3 files changed, 863 insertions(+), 352 deletions(-) diff --git a/lib/littlefs/README.md b/lib/littlefs/README.md index 1f0aacbf98..ca21a05f42 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 6bae806ceb..31f3c603e7 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 @@ relocate: 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 @@ compact: 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 @@ relocate: 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 @@ relocate: 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 @@ relocate: 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 @@ relocate: 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 @@ relocate: } 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; -} -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); +} +#endif + +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 c89af79cab..e2afc7792c 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 245/337] 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 31f3c603e7..1f8c9d2d93 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 246/337] 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 3d041e1c14..44372b0e8a 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 247/337] 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 58fee7c92b..302c52794d 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 248/337] 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 968b2999ce..5003bb27cc 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 249/337] 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 a123044892..d315ea6973 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 250/337] 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 b89e462e04..041074557e 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 f6f2fcaf4a..f4ce1f8212 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 ded9fab922..32fccf071d 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 251/337] 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 32fccf071d..1a413ec991 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 252/337] 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 3ab3ff334c..ac9e0bbc44 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 253/337] 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 3bfe64d753..eb8477cbb1 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 254/337] 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 192d34514b..721b80508e 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. + Convert the bytes in the *data* object to a hexadecimal representation. + Returns a bytes object. - .. admonition:: Difference to CPython - :class: attention - - 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 1d4c72b24b..9e4f86fbd2 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 255/337] 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 3d8cec6075..bea8a2723a 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 @@ class RAMBlockDevice: 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 @@ usys.path.append("/lfs") 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 aa654ebe05..68561b4807 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 256/337] 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 3cb7af1b43..7dca59d351 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 257/337] 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 8078e29e0c..649bcda108 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 def66481cb..3e55069ab9 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 258/337] 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 00b7213869..2160e0ac99 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 259/337] 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 0aae74e116..e8892b5746 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 9b4b4fb83e..773a30802f 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 260/337] 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 3793ef5ff2..5fa22d7c78 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 4ae6022bc3..6f2e61e099 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 8d8bb8f4ee..91f064835e 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 83a144f8fe..1a3fca3ac8 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 9c20f0f954..d9e5a64da5 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 261/337] 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 d00c2ec713..fb10b96501 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 c8805d6829..6581860ff3 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 490fc4a09b..73058aad6b 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 262/337] 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 e870d39f5f..bb15bd6252 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 96dfdeeab6..c4162ff21c 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 f6e130eb57..29164ac11c 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 e1fe93bb0e..843b987cee 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 2831187531..ad62f761e5 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 8890abc59b..60fbc35fcb 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 05c8819a77..5090b43b1c 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 263/337] 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 6c5ff721ec..d9bec93c27 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 29164ac11c..45d968f353 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 264/337] 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 f2623eabd3..d3978718e0 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 f4384a8854..6671b3abdc 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 24269c3b98..c00da5ed9e 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 265/337] 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 d315ea6973..e1b1a059da 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 4ace0f50e8..43065df4ae 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 de9d0bf1c3..a83450afe3 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 06d0dc17bf..1186bd91a0 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 f56f98fae0..ff9ef577c5 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 111a9d19ff..a60ca955d7 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 266/337] 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 e1b1a059da..8a38b7534f 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 267/337] 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 613e325a95..843886d009 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 268/337] 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 843886d009..9ceeab7d7b 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 0ce69486534eca545e5a86f9696047956b5c874e Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 15 Jan 2021 14:56:57 +1100 Subject: [PATCH 269/337] lib/libhydrogen: Add new libhydrogen submodule. This library is a small and easy-to-use cryptographic library which is well suited to embedded systems. Signed-off-by: Damien George --- .gitmodules | 3 +++ lib/libhydrogen | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/libhydrogen diff --git a/.gitmodules b/.gitmodules index c152e9e774..f4785955af 100644 --- a/.gitmodules +++ b/.gitmodules @@ -36,3 +36,6 @@ [submodule "lib/nxp_driver"] path = lib/nxp_driver url = https://github.com/hathach/nxp_driver.git +[submodule "lib/libhydrogen"] + path = lib/libhydrogen + url = https://github.com/jedisct1/libhydrogen.git diff --git a/lib/libhydrogen b/lib/libhydrogen new file mode 160000 index 0000000000..5c5d513093 --- /dev/null +++ b/lib/libhydrogen @@ -0,0 +1 @@ +Subproject commit 5c5d513093075f7245ea522101b17c50aa579af2 From 09e67de32739c032bf357e253ae5a783bd41dd7b Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 15 Jan 2021 02:55:40 +1100 Subject: [PATCH 270/337] stm32/mboot/gzstream: Fix lost data decompressing final part of file. Prior to this fix, the final piece of data in a compressed file may have been lost when decompressing. Signed-off-by: Damien George --- ports/stm32/mboot/gzstream.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/ports/stm32/mboot/gzstream.c b/ports/stm32/mboot/gzstream.c index 20ba33045f..4f8a4a40cc 100644 --- a/ports/stm32/mboot/gzstream.c +++ b/ports/stm32/mboot/gzstream.c @@ -38,7 +38,7 @@ typedef struct _gz_stream_t { void *stream_data; stream_read_t stream_read; - TINF_DATA tinf; + struct uzlib_uncomp tinf; uint8_t buf[512]; uint8_t dict[DICT_SIZE]; } gz_stream_t; @@ -66,7 +66,7 @@ int gz_stream_init(void *stream_data, stream_read_t stream_read) { gz_stream.stream_read = stream_read; memset(&gz_stream.tinf, 0, sizeof(gz_stream.tinf)); - gz_stream.tinf.readSource = gz_stream_read_src; + gz_stream.tinf.source_read_cb = gz_stream_read_src; int st = uzlib_gzip_parse_header(&gz_stream.tinf); if (st != TINF_OK) { @@ -79,15 +79,21 @@ int gz_stream_init(void *stream_data, stream_read_t stream_read) { } int gz_stream_read(size_t len, uint8_t *buf) { + if (gz_stream.tinf.source == NULL && gz_stream.tinf.source_read_cb == NULL) { + // End of stream. + return 0; + } gz_stream.tinf.dest = buf; gz_stream.tinf.dest_limit = buf + len; int st = uzlib_uncompress_chksum(&gz_stream.tinf); - if (st == TINF_DONE) { - return 0; - } if (st < 0) { return st; } + if (st == TINF_DONE) { + // Indicate end-of-stream for subsequent calls. + gz_stream.tinf.source = NULL; + gz_stream.tinf.source_read_cb = NULL; + } return gz_stream.tinf.dest - buf; } From c6f334272a34e007462347f067013944e57bfe33 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 15 Jan 2021 03:00:40 +1100 Subject: [PATCH 271/337] stm32/mboot: Add support for signed and encrypted firmware updates. This commit adds support to stm32's mboot for signe, encrypted and compressed DFU updates. It is based on inital work done by Andrew Leech. The feature is enabled by setting MBOOT_ENABLE_PACKING to 1 in the board's mpconfigboard.mk file, and by providing a header file in the board folder (usually called mboot_keys.h) with a set of signing and encryption keys (which can be generated by mboot_pack_dfu.py). The signing and encryption is provided by libhydrogen. Compression is provided by uzlib. Enabling packing costs about 3k of flash. The included mboot_pack_dfu.py script converts a .dfu file to a .pack.dfu file which can be subsequently deployed to a board with mboot in packing mode. This .pack.dfu file is created as follows: - the firmware from the original .dfu is split into chunks (so the decryption can fit in RAM) - each chunk is compressed, encrypted, a header added, then signed - a special final chunk is added with a signature of the entire firmware - all chunks are concatenated to make the final .pack.dfu file The .pack.dfu file can be deployed over USB or from the internal filesystem on the device (if MBOOT_FSLOAD is enabled). See #5267 and #5309 for additional discussion. Signed-off-by: Damien George --- ports/stm32/Makefile | 24 ++- ports/stm32/mboot/Makefile | 16 ++ ports/stm32/mboot/Particle.h | 10 + ports/stm32/mboot/README.md | 28 +++ ports/stm32/mboot/dfu.h | 10 +- ports/stm32/mboot/fsload.c | 45 ++++- ports/stm32/mboot/fwupdate.py | 7 +- ports/stm32/mboot/gzstream.c | 16 +- ports/stm32/mboot/gzstream.h | 3 +- ports/stm32/mboot/main.c | 68 ++++++- ports/stm32/mboot/mboot.h | 7 + ports/stm32/mboot/mboot_pack_dfu.py | 260 +++++++++++++++++++++++++ ports/stm32/mboot/pack.c | 282 ++++++++++++++++++++++++++++ ports/stm32/mboot/pack.h | 82 ++++++++ ports/stm32/mboot/stm32_generic.ld | 4 +- ports/stm32/mboot/vfs_fat.c | 2 +- ports/stm32/mboot/vfs_lfs.c | 4 +- ports/stm32/mpconfigport.mk | 7 +- 18 files changed, 844 insertions(+), 31 deletions(-) create mode 100644 ports/stm32/mboot/Particle.h create mode 100644 ports/stm32/mboot/mboot_pack_dfu.py create mode 100644 ports/stm32/mboot/pack.c create mode 100644 ports/stm32/mboot/pack.h diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index a9dac03d2c..d8c9772b26 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -30,7 +30,7 @@ FROZEN_MANIFEST ?= boards/manifest.py # include py core make definitions include $(TOP)/py/py.mk -GIT_SUBMODULES = lib/lwip lib/mbedtls lib/mynewt-nimble lib/stm32lib +GIT_SUBMODULES = lib/libhydrogen lib/lwip lib/mbedtls lib/mynewt-nimble lib/stm32lib MCU_SERIES_UPPER = $(shell echo $(MCU_SERIES) | tr '[:lower:]' '[:upper:]') CMSIS_MCU_LOWER = $(shell echo $(CMSIS_MCU) | tr '[:upper:]' '[:lower:]') @@ -41,6 +41,7 @@ HAL_DIR=lib/stm32lib/STM32$(MCU_SERIES_UPPER)xx_HAL_Driver USBDEV_DIR=usbdev #USBHOST_DIR=usbhost DFU=$(TOP)/tools/dfu.py +MBOOT_PACK_DFU = mboot/mboot_pack_dfu.py # may need to prefix dfu-util with sudo USE_PYDFU ?= 1 PYDFU ?= $(TOP)/tools/pydfu.py @@ -546,7 +547,13 @@ $(PY_BUILD)/formatfloat.o: COPT += -Os $(PY_BUILD)/parsenum.o: COPT += -Os $(PY_BUILD)/mpprint.o: COPT += -Os -all: $(TOP)/lib/stm32lib/README.md $(BUILD)/firmware.dfu $(BUILD)/firmware.hex +all: $(TOP)/lib/stm32lib/README.md all_main $(BUILD)/firmware.hex + +ifeq ($(MBOOT_ENABLE_PACKING),1) +all_main: $(BUILD)/firmware.pack.dfu +else +all_main: $(BUILD)/firmware.dfu +endif # For convenience, automatically fetch required submodules if they don't exist $(TOP)/lib/stm32lib/README.md: @@ -607,6 +614,11 @@ define GENERATE_DFU $(1) endef +define GENERATE_PACK_DFU + $(ECHO) "GEN $(1)" + $(Q)$(PYTHON) $(MBOOT_PACK_DFU) --keys $(MBOOT_PACK_KEYS_FILE) pack-dfu --gzip $(MBOOT_PACK_CHUNKSIZE) $(2) $(1) +endef + define GENERATE_HEX $(ECHO) "GEN $(1)" $(Q)$(OBJCOPY) -O ihex $(2) $(1) @@ -614,8 +626,13 @@ endef .PHONY: deploy deploy-stlink deploy-openocd +ifeq ($(MBOOT_ENABLE_PACKING),1) +deploy: $(BUILD)/firmware.pack.dfu + $(call RUN_DFU,$^) +else deploy: $(BUILD)/firmware.dfu $(call RUN_DFU,$^) +endif # 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 @@ -662,6 +679,9 @@ $(BUILD)/firmware.dfu: $(BUILD)/firmware0.bin $(BUILD)/firmware1.bin $(call GENERATE_DFU,$@,$(word 1,$^),$(TEXT0_ADDR),$(word 2,$^),$(TEXT1_ADDR)) endif +$(BUILD)/firmware.pack.dfu: $(BUILD)/firmware.dfu $(BOARD_DIR)/mboot_keys.h + $(call GENERATE_PACK_DFU,$@,$<) + $(BUILD)/firmware.hex: $(BUILD)/firmware.elf $(call GENERATE_HEX,$@,$^) diff --git a/ports/stm32/mboot/Makefile b/ports/stm32/mboot/Makefile index 44372b0e8a..ad2fda3eee 100755 --- a/ports/stm32/mboot/Makefile +++ b/ports/stm32/mboot/Makefile @@ -12,6 +12,12 @@ BOARD_DIR ?= $(abspath ../boards/$(BOARD)) # that can be built with or without mboot. USE_MBOOT ?= 1 +# Set MBOOT_ENABLE_PACKING to 1 to enable DFU packing with encryption and signing. +# Ensure the MBOOT_PACK_xxx values match stm32/Makefile, to build matching application firmware. +MBOOT_ENABLE_PACKING ?= 0 +MBOOT_PACK_CHUNKSIZE ?= 16384 +MBOOT_PACK_KEYS_FILE ?= $(BOARD_DIR)/mboot_keys.h + # Sanity check that the board configuration directory exists ifeq ($(wildcard $(BOARD_DIR)/.),) $(error Invalid BOARD specified: $(BOARD_DIR)) @@ -110,6 +116,7 @@ SRC_C = \ elem.c \ fsload.c \ gzstream.c \ + pack.c \ vfs_fat.c \ vfs_lfs.c \ drivers/bus/softspi.c \ @@ -129,6 +136,15 @@ SRC_O = \ $(SYSTEM_FILE) \ ports/stm32/resethandler.o \ +ifeq ($(MBOOT_ENABLE_PACKING), 1) + +SRC_C += lib/libhydrogen/hydrogen.c + +CFLAGS += -DMBOOT_ENABLE_PACKING=1 -DPARTICLE -DPLATFORM_ID=3 +CFLAGS += -DMBOOT_PACK_CHUNKSIZE=$(MBOOT_PACK_CHUNKSIZE) +CFLAGS += -DMBOOT_PACK_KEYS_FILE=\"$(MBOOT_PACK_KEYS_FILE)\" +endif + $(BUILD)/$(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_ll_usb.o: CFLAGS += -Wno-attributes SRC_HAL = $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_,\ hal_cortex.c \ diff --git a/ports/stm32/mboot/Particle.h b/ports/stm32/mboot/Particle.h new file mode 100644 index 0000000000..5e0953739a --- /dev/null +++ b/ports/stm32/mboot/Particle.h @@ -0,0 +1,10 @@ +// Header for libhydrogen use only. Act like a Particle board for the random +// implementation. This code is not actually called when just decrypting and +// verifying a signature, but a correct implementation is provided anyway. + +#include "py/mphal.h" +#include "rng.h" + +static inline uint32_t HAL_RNG_GetRandomNumber(void) { + return rng_get(); +} diff --git a/ports/stm32/mboot/README.md b/ports/stm32/mboot/README.md index d8aa6d456f..59213eb615 100644 --- a/ports/stm32/mboot/README.md +++ b/ports/stm32/mboot/README.md @@ -155,6 +155,34 @@ firmware.dfu.gz stored on the default FAT filesystem: The 0x80000000 value is the address understood by Mboot as the location of the external SPI flash, configured via `MBOOT_SPIFLASH_ADDR`. +Signed and encrypted DFU support +-------------------------------- + +Mboot optionally supports signing and encrypting the binary firmware in the DFU file. +In general this is refered to as a packed DFU file. This requires additional settings +in the board config and requires the `pyhy` Python module to be installed for `python3` +to be used when building packed firmware, eg: + + $ pip3 install pyhy + +In addition to the changes made to mpconfigboard.mk earlier, for encrypted +support you also need to add: + + MBOOT_ENABLE_PACKING = 1 + +You will also need to generate signing and encryption keys which will be built into +mboot and used for all subsequent installations of firmware. This can be done via: + + $ python3 ports/stm32/mboot/mboot_pack_dfu.py generate-keys + +This command generates a `mboot_keys.h` file which should be stored in the board +definition folder (next to mpconfigboard.mk). + +Once you build the firmware, the `firmware.pack.dfu` file will contain the encrypted +and signed firmware, and can be deployed via USB DFU, or by copying it to the device's +internal filesystem (if `MBOOT_FSLOAD` is enabled). `firmware.dfu` is still unencrypted +and can be directly flashed with jtag etc. + Example: Mboot on PYBv1.x ------------------------- diff --git a/ports/stm32/mboot/dfu.h b/ports/stm32/mboot/dfu.h index a1d4d10d0e..73db3545d8 100644 --- a/ports/stm32/mboot/dfu.h +++ b/ports/stm32/mboot/dfu.h @@ -39,6 +39,14 @@ #define MBOOT_ERROR_STR_INVALID_ADDRESS_IDX 0x11 #define MBOOT_ERROR_STR_INVALID_ADDRESS "Address out of range" +#if MBOOT_ENABLE_PACKING +#define MBOOT_ERROR_STR_INVALID_SIG_IDX 0x12 +#define MBOOT_ERROR_STR_INVALID_SIG "Invalid signature in file" + +#define MBOOT_ERROR_STR_INVALID_READ_IDX 0x13 +#define MBOOT_ERROR_STR_INVALID_READ "Read support disabled on encrypted bootloader" +#endif + // DFU class requests enum { DFU_DETACH = 0, @@ -104,6 +112,6 @@ typedef struct _dfu_state_t { uint8_t buf[DFU_XFER_SIZE] __attribute__((aligned(4))); } dfu_context_t; -static dfu_context_t dfu_context SECTION_NOZERO_BSS; +extern dfu_context_t dfu_context; #endif // MICROPY_INCLUDED_STM32_MBOOT_DFU_H diff --git a/ports/stm32/mboot/fsload.c b/ports/stm32/mboot/fsload.c index 1e1ad7a04d..591b670aa0 100644 --- a/ports/stm32/mboot/fsload.c +++ b/ports/stm32/mboot/fsload.c @@ -28,6 +28,7 @@ #include "py/mphal.h" #include "mboot.h" +#include "pack.h" #include "vfs.h" #if MBOOT_FSLOAD @@ -36,13 +37,43 @@ #error Must enable at least one VFS component #endif +#if MBOOT_ENABLE_PACKING +// Packed DFU files are gzip'd internally, not on the outside, so reads of the file +// just read the file directly. + +static void *input_stream_data; +static stream_read_t input_stream_read_meth; + +static inline int input_stream_init(void *stream_data, stream_read_t stream_read) { + input_stream_data = stream_data; + input_stream_read_meth = stream_read; + return 0; +} + +static inline int input_stream_read(size_t len, uint8_t *buf) { + return input_stream_read_meth(input_stream_data, buf, len); +} + +#else +// Standard (non-packed) DFU files must be gzip'd externally / on the outside, so +// reads of the file go through gz_stream. + +static inline int input_stream_init(void *stream_data, stream_read_t stream_read) { + return gz_stream_init_from_stream(stream_data, stream_read); +} + +static inline int input_stream_read(size_t len, uint8_t *buf) { + return gz_stream_read(len, buf); +} +#endif + static int fsload_program_file(bool write_to_flash) { // Parse DFU uint8_t buf[512]; size_t file_offset; // Read file header, <5sBIB - int res = gz_stream_read(11, buf); + int res = input_stream_read(11, buf); if (res != 11) { return -1; } @@ -62,7 +93,7 @@ static int fsload_program_file(bool write_to_flash) { uint32_t total_size = get_le32(buf + 6); // Read target header, <6sBi255sII - res = gz_stream_read(274, buf); + res = input_stream_read(274, buf); if (res != 274) { return -1; } @@ -82,7 +113,7 @@ static int fsload_program_file(bool write_to_flash) { // Parse each element for (size_t elem = 0; elem < num_elems; ++elem) { // Read element header, sizeof(buf)) { l = sizeof(buf); } - res = gz_stream_read(l, buf); + res = input_stream_read(l, buf); if (res != l) { return -1; } @@ -135,7 +168,7 @@ static int fsload_program_file(bool write_to_flash) { } // Read trailing info - res = gz_stream_read(16, buf); + res = input_stream_read(16, buf); if (res != 16) { return -1; } @@ -151,7 +184,7 @@ static int fsload_validate_and_program_file(void *stream, const stream_methods_t led_state_all(pass == 0 ? 2 : 4); int res = meth->open(stream, fname); if (res == 0) { - res = gz_stream_init(stream, meth->read); + res = input_stream_init(stream, meth->read); if (res == 0) { res = fsload_program_file(pass == 0 ? false : true); } diff --git a/ports/stm32/mboot/fwupdate.py b/ports/stm32/mboot/fwupdate.py index dab5fa6632..65284a6ac0 100644 --- a/ports/stm32/mboot/fwupdate.py +++ b/ports/stm32/mboot/fwupdate.py @@ -157,14 +157,15 @@ def update_mboot(filename): def update_mpy(filename, fs_base, fs_len, fs_type=VFS_FAT): - # Check firmware is of .dfu.gz type + # Check firmware is of .dfu or .dfu.gz type try: with open(filename, "rb") as f: hdr = uzlib.DecompIO(f, 16 + 15).read(6) except Exception: - hdr = None + with open(filename, "rb") as f: + hdr = f.read(6) if hdr != b"DfuSe\x01": - print("Firmware must be a .dfu.gz file.") + print("Firmware must be a .dfu(.gz) file.") return ELEM_TYPE_END = 1 diff --git a/ports/stm32/mboot/gzstream.c b/ports/stm32/mboot/gzstream.c index 4f8a4a40cc..6530539f40 100644 --- a/ports/stm32/mboot/gzstream.c +++ b/ports/stm32/mboot/gzstream.c @@ -31,7 +31,7 @@ #include "gzstream.h" #include "mboot.h" -#if MBOOT_FSLOAD +#if MBOOT_FSLOAD || MBOOT_ENABLE_PACKING #define DICT_SIZE (1 << 15) @@ -61,7 +61,17 @@ static int gz_stream_read_src(TINF_DATA *tinf) { return gz_stream.buf[0]; } -int gz_stream_init(void *stream_data, stream_read_t stream_read) { +int gz_stream_init_from_raw_data(const uint8_t *data, size_t len) { + memset(&gz_stream.tinf, 0, sizeof(gz_stream.tinf)); + gz_stream.tinf.source = data; + gz_stream.tinf.source_limit = data + len; + + uzlib_uncompress_init(&gz_stream.tinf, gz_stream.dict, DICT_SIZE); + + return 0; +} + +int gz_stream_init_from_stream(void *stream_data, stream_read_t stream_read) { gz_stream.stream_data = stream_data; gz_stream.stream_read = stream_read; @@ -97,4 +107,4 @@ int gz_stream_read(size_t len, uint8_t *buf) { return gz_stream.tinf.dest - buf; } -#endif // MBOOT_FSLOAD +#endif // MBOOT_FSLOAD || MBOOT_ENABLE_PACKING diff --git a/ports/stm32/mboot/gzstream.h b/ports/stm32/mboot/gzstream.h index ec11ba79bb..d3d93aa657 100644 --- a/ports/stm32/mboot/gzstream.h +++ b/ports/stm32/mboot/gzstream.h @@ -39,7 +39,8 @@ typedef struct _stream_methods_t { stream_read_t read; } stream_methods_t; -int gz_stream_init(void *stream_data, stream_read_t stream_read); +int gz_stream_init_from_raw_data(const uint8_t *data, size_t len); +int gz_stream_init_from_stream(void *stream_data, stream_read_t stream_read); int gz_stream_read(size_t len, uint8_t *buf); #endif // MICROPY_INCLUDED_STM32_MBOOT_GZSTREAM_H diff --git a/ports/stm32/mboot/main.c b/ports/stm32/mboot/main.c index 99187e3ef8..0846d97cf3 100644 --- a/ports/stm32/mboot/main.c +++ b/ports/stm32/mboot/main.c @@ -37,6 +37,7 @@ #include "mboot.h" #include "powerctrl.h" #include "dfu.h" +#include "pack.h" // This option selects whether to use explicit polling or IRQs for USB events. // In some test cases polling mode can run slightly faster, but it uses more power. @@ -56,11 +57,17 @@ // Configure PLL to give the desired CPU freq #undef MICROPY_HW_FLASH_LATENCY #if defined(STM32F4) || defined(STM32F7) -#define CORE_PLL_FREQ (48000000) -#define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_1 + #if MBOOT_ENABLE_PACKING + // With encryption/signing/compression, a faster CPU makes processing much faster. + #define CORE_PLL_FREQ (96000000) + #define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_3 + #else + #define CORE_PLL_FREQ (48000000) + #define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_1 + #endif #elif defined(STM32H7) -#define CORE_PLL_FREQ (96000000) -#define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_2 + #define CORE_PLL_FREQ (96000000) + #define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_2 #endif #undef MICROPY_HW_CLK_PLLM #undef MICROPY_HW_CLK_PLLN @@ -87,7 +94,8 @@ // These bits are used to detect valid application firmware at APPLICATION_ADDR #define APP_VALIDITY_BITS (0x00000003) -#define MP_ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +// Global dfu state +dfu_context_t dfu_context SECTION_NOZERO_BSS; static void do_reset(void); @@ -140,7 +148,7 @@ void HAL_Delay(uint32_t ms) { mp_hal_delay_ms(ms); } -static void __fatal_error(const char *msg) { +NORETURN static void __fatal_error(const char *msg) { NVIC_SystemReset(); for (;;) { } @@ -547,7 +555,7 @@ static int spiflash_page_erase(mp_spiflash_t *spif, uint32_t addr, uint32_t n_bl } #endif -int do_page_erase(uint32_t addr, uint32_t *next_addr) { +int hw_page_erase(uint32_t addr, uint32_t *next_addr) { int ret = -1; led0_state(LED0_STATE_ON); @@ -573,7 +581,7 @@ int do_page_erase(uint32_t addr, uint32_t *next_addr) { return ret; } -void do_read(uint32_t addr, int len, uint8_t *buf) { +void hw_read(uint32_t addr, int len, uint8_t *buf) { led0_state(LED0_STATE_FAST_FLASH); #if defined(MBOOT_SPIFLASH_ADDR) if (MBOOT_SPIFLASH_ADDR <= addr && addr < MBOOT_SPIFLASH_ADDR + MBOOT_SPIFLASH_BYTE_SIZE) { @@ -592,7 +600,7 @@ void do_read(uint32_t addr, int len, uint8_t *buf) { led0_state(LED0_STATE_SLOW_FLASH); } -int do_write(uint32_t addr, const uint8_t *src8, size_t len) { +int hw_write(uint32_t addr, const uint8_t *src8, size_t len) { int ret = -1; led0_state(LED0_STATE_FAST_FLASH); #if defined(MBOOT_SPIFLASH_ADDR) @@ -616,6 +624,34 @@ int do_write(uint32_t addr, const uint8_t *src8, size_t len) { return ret; } +int do_page_erase(uint32_t addr, uint32_t *next_addr) { + #if MBOOT_ENABLE_PACKING + // Erase handled automatically for packed mode. + return 0; + #else + return hw_page_erase(addr, next_addr); + #endif +} + +void do_read(uint32_t addr, int len, uint8_t *buf) { + #if MBOOT_ENABLE_PACKING + // Read disabled on packed (encrypted) mode. + dfu_context.status = DFU_STATUS_ERROR_FILE; + dfu_context.error = MBOOT_ERROR_STR_INVALID_READ_IDX; + led0_state(LED0_STATE_SLOW_INVERTED_FLASH); + #else + hw_read(addr, len, buf); + #endif +} + +int do_write(uint32_t addr, const uint8_t *src8, size_t len) { + #if MBOOT_ENABLE_PACKING + return mboot_pack_write(addr, src8, len); + #else + return hw_write(addr, src8, len); + #endif +} + /******************************************************************************/ // I2C slave interface @@ -1068,6 +1104,16 @@ static uint8_t *pyb_usbdd_StrDescriptor(USBD_HandleTypeDef *pdev, uint8_t idx, u USBD_GetString((uint8_t*)MBOOT_ERROR_STR_INVALID_ADDRESS, str_desc, length); return str_desc; + #if MBOOT_ENABLE_PACKING + case MBOOT_ERROR_STR_INVALID_SIG_IDX: + USBD_GetString((uint8_t*)MBOOT_ERROR_STR_INVALID_SIG, str_desc, length); + return str_desc; + + case MBOOT_ERROR_STR_INVALID_READ_IDX: + USBD_GetString((uint8_t*)MBOOT_ERROR_STR_INVALID_READ, str_desc, length); + return str_desc; + #endif + default: return NULL; } @@ -1388,6 +1434,10 @@ enter_bootloader: mp_spiflash_init(MBOOT_SPIFLASH2_SPIFLASH); #endif + #if MBOOT_ENABLE_PACKING + mboot_pack_init(); + #endif + #if MBOOT_FSLOAD if ((initial_r0 & 0xffffff80) == 0x70ad0080) { // Application passed through elements, validate then process them diff --git a/ports/stm32/mboot/mboot.h b/ports/stm32/mboot/mboot.h index 37929665eb..e4ed3cecc7 100644 --- a/ports/stm32/mboot/mboot.h +++ b/ports/stm32/mboot/mboot.h @@ -36,6 +36,9 @@ #define ELEM_DATA_START (&_estack[0]) #define ELEM_DATA_MAX (&_estack[ELEM_DATA_SIZE]) +#define NORETURN __attribute__((noreturn)) +#define MP_ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) + enum { ELEM_TYPE_END = 1, ELEM_TYPE_MOUNT, @@ -53,6 +56,10 @@ extern uint8_t _estack[ELEM_DATA_SIZE]; uint32_t get_le32(const uint8_t *b); void led_state_all(unsigned int mask); +int hw_page_erase(uint32_t addr, uint32_t *next_addr); +void hw_read(uint32_t addr, int len, uint8_t *buf); +int hw_write(uint32_t addr, const uint8_t *src8, size_t len); + int do_page_erase(uint32_t addr, uint32_t *next_addr); void do_read(uint32_t addr, int len, uint8_t *buf); int do_write(uint32_t addr, const uint8_t *src8, size_t len); diff --git a/ports/stm32/mboot/mboot_pack_dfu.py b/ports/stm32/mboot/mboot_pack_dfu.py new file mode 100644 index 0000000000..47382f5910 --- /dev/null +++ b/ports/stm32/mboot/mboot_pack_dfu.py @@ -0,0 +1,260 @@ +# This file is part of the MicroPython project, http://micropython.org/ +# +# The MIT License (MIT) +# +# Copyright (c) 2020-2021 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. +""" +Utility to create compressed, encrypted and signed DFU files. +""" + +import argparse +import os +import re +import struct +import sys +import zlib + +sys.path.append(os.path.dirname(__file__) + "/../../../tools") +import dfu + +try: + import pyhy +except ImportError: + raise SystemExit( + "ERROR: pyhy not found. Please install python pyhy for encrypted mboot support: pip3 install pyhy" + ) + + +# Currenty supported version of a packed DFU file. +MBOOT_PACK_HEADER_VERSION = 1 + +# Must match MBOOT_PACK_HYDRO_CONTEXT in mboot/pack.h +MBOOT_PACK_HYDRO_CONTEXT = "mbootenc" + +# Must match enum in mboot/pack.h. +MBOOT_PACK_CHUNK_META = 0 +MBOOT_PACK_CHUNK_FULL_SIG = 1 +MBOOT_PACK_CHUNK_FW_RAW = 2 +MBOOT_PACK_CHUNK_FW_GZIP = 3 + + +class Keys: + def __init__(self, filename): + self.filename = filename + + def generate(self): + kp = pyhy.hydro_sign_keygen() + self.sign_sk = kp.sk + self.sign_pk = kp.pk + self.secretbox = pyhy.hydro_secretbox_keygen() + + def _save_data(self, name, data, file_, hide=False): + prefix = "//" if hide else "" + data = ",".join("0x{:02x}".format(b) for b in data) + file_.write("{}const uint8_t {}[] = {{{}}};\n".format(prefix, name, data)) + + def _load_data(self, name, line): + line = line.split(name + "[] = ") + if len(line) != 2: + raise Exception("malformed input keys: {}".format(line)) + data = line[1].strip() + return bytes(int(value, 16) for value in data[1:-2].split(",")) + + def save(self): + with open(self.filename, "w") as f: + self._save_data("mboot_pack_sign_secret_key", self.sign_sk, f, hide=True) + self._save_data("mboot_pack_sign_public_key", self.sign_pk, f) + self._save_data("mboot_pack_secretbox_key", self.secretbox, f) + + def load(self): + with open(self.filename) as f: + self.sign_sk = self._load_data("mboot_pack_sign_secret_key", f.readline()) + self.sign_pk = self._load_data("mboot_pack_sign_public_key", f.readline()) + self.secretbox = self._load_data("mboot_pack_secretbox_key", f.readline()) + + +def dfu_read(filename): + elems = [] + + with open(filename, "rb") as f: + hdr = f.read(11) + sig, ver, size, num_targ = struct.unpack("<5sBIB", hdr) + file_offset = 11 + + for i in range(num_targ): + hdr = f.read(274) + sig, alt, has_name, name, t_size, num_elem = struct.unpack("<6sBi255sII", hdr) + + file_offset += 274 + file_offset_t = file_offset + for j in range(num_elem): + hdr = f.read(8) + addr, e_size = struct.unpack(" + +#include "dfu.h" +#include "gzstream.h" +#include "mboot.h" +#include "pack.h" + +#if MBOOT_ENABLE_PACKING + +// Keys provided externally by the board, will be built into mboot flash. +#include MBOOT_PACK_KEYS_FILE + +// Encrypted dfu files using gzip require a decompress buffer. Larger can be faster. +// This setting is independent to the incoming encrypted/signed/compressed DFU file. +#ifndef MBOOT_PACK_GZIP_BUFFER_SIZE +#define MBOOT_PACK_GZIP_BUFFER_SIZE (2048) +#endif + +// State to manage automatic flash erasure. +static uint32_t erased_base_addr; +static uint32_t erased_top_addr; + +// DFU chunk buffer, used to cache incoming blocks of data from USB. +static uint32_t firmware_chunk_base_addr; +static mboot_pack_chunk_buf_t firmware_chunk_buf; + +// Temporary buffer for decrypted data. +static uint8_t decrypted_buf[MBOOT_PACK_DFU_CHUNK_BUF_SIZE] __attribute__((aligned(8))); + +// Temporary buffer for uncompressing. +static uint8_t uncompressed_buf[MBOOT_PACK_GZIP_BUFFER_SIZE] __attribute__((aligned(8))); + +// Buffer to hold the start of the firmware, which is only written once the +// entire firmware is validated. This is 8 bytes due to STM32WB MCUs requiring +// that a double-word write to flash can only be done once (due to ECC). +static uint8_t firmware_head[8]; + +void mboot_pack_init(void) { + erased_base_addr = 0; + erased_top_addr = 0; + firmware_chunk_base_addr = 0; +} + +// In encrypted mode the erase is automatically managed. +// Note: this scheme requires blocks be written in sequence, which is the case. +static int mboot_pack_erase(uint32_t addr, size_t len) { + while (!(erased_base_addr <= addr && addr + len <= erased_top_addr)) { + uint32_t erase; + if (erased_base_addr <= addr && addr < erased_top_addr) { + erase = erased_top_addr; + } else { + erase = addr; + erased_base_addr = addr; + } + uint32_t next_addr; + int ret = hw_page_erase(erase, &next_addr); + if (ret != 0) { + return ret; + } + erased_top_addr = next_addr; + } + return 0; +} + +// Commit an unencrypted and uncompressed chunk of firmware to the flash. +static int mboot_pack_commit_chunk(uint32_t addr, uint8_t *data, size_t len) { + // Erase any required sectors before writing. + int ret = mboot_pack_erase(addr, len); + if (ret != 0) { + return ret; + } + + if (addr == APPLICATION_ADDR) { + // Don't write the very start of the firmware, just copy it into a temporary buffer. + // It will be written only if the full firmware passes the checksum/signature. + memcpy(firmware_head, data, sizeof(firmware_head)); + addr += sizeof(firmware_head); + data += sizeof(firmware_head); + len -= sizeof(firmware_head); + } + + // Commit this piece of the firmware. + return hw_write(addr, data, len); +} + +// Handle a chunk with the full firmware signature. +static int mboot_pack_handle_full_sig(void) { + if (firmware_chunk_buf.header.length < hydro_sign_BYTES) { + return -1; + } + + uint8_t *full_sig = &firmware_chunk_buf.data[firmware_chunk_buf.header.length - hydro_sign_BYTES]; + uint32_t *region_data = (uint32_t *)&firmware_chunk_buf.data[0]; + size_t num_regions = (full_sig - (uint8_t *)region_data) / sizeof(uint32_t) / 2; + + uint8_t *buf = decrypted_buf; + const size_t buf_alloc = sizeof(decrypted_buf); + + // Compute the signature of the full firmware. + hydro_sign_state sign_state; + hydro_sign_init(&sign_state, MBOOT_PACK_HYDRO_CONTEXT); + for (size_t region = 0; region < num_regions; ++region) { + uint32_t addr = region_data[2 * region]; + uint32_t len = region_data[2 * region + 1]; + while (len) { + uint32_t l = len <= buf_alloc ? len : buf_alloc; + hw_read(addr, l, buf); + if (addr == APPLICATION_ADDR) { + // The start of the firmware was not yet written to flash so copy + // it out of the temporary buffer to compute the full signature. + memcpy(buf, firmware_head, sizeof(firmware_head)); + } + int ret = hydro_sign_update(&sign_state, buf, l); + if (ret != 0) { + return -1; + } + addr += l; + len -= l; + } + } + + // Verify the signature of the full firmware. + int ret = hydro_sign_final_verify(&sign_state, full_sig, mboot_pack_sign_public_key); + if (ret != 0) { + dfu_context.status = DFU_STATUS_ERROR_VERIFY; + dfu_context.error = MBOOT_ERROR_STR_INVALID_SIG_IDX; + return -1; + } + + // Full firmware passed the signature check. + // Write the start of the firmware so it boots. + return hw_write(APPLICATION_ADDR, firmware_head, sizeof(firmware_head)); +} + +// Handle a chunk with firmware data. +static int mboot_pack_handle_firmware(void) { + const uint8_t *fw_data = &firmware_chunk_buf.data[0]; + const size_t fw_len = firmware_chunk_buf.header.length; + + // Decrypt the chunk. + if (hydro_secretbox_decrypt(decrypted_buf, fw_data, fw_len, 0, MBOOT_PACK_HYDRO_CONTEXT, mboot_pack_secretbox_key) != 0) { + dfu_context.status = DFU_STATUS_ERROR_VERIFY; + dfu_context.error = MBOOT_ERROR_STR_INVALID_SIG_IDX; + return -1; + } + + // Use the decrypted message contents going formward. + size_t len = fw_len - hydro_secretbox_HEADERBYTES; + uint32_t addr = firmware_chunk_buf.header.address; + + if (firmware_chunk_buf.header.format == MBOOT_PACK_CHUNK_FW_GZIP) { + // Decompress chunk data. + gz_stream_init_from_raw_data(decrypted_buf, len); + for (;;) { + int read = gz_stream_read(sizeof(uncompressed_buf), uncompressed_buf); + if (read == 0) { + return 0; // finished decompressing + } else if (read < 0) { + return -1; // error reading + } + int ret = mboot_pack_commit_chunk(addr, uncompressed_buf, read); + if (ret != 0) { + return ret; + } + addr += read; + } + } else { + // Commit chunk data directly. + return mboot_pack_commit_chunk(addr, decrypted_buf, len); + } +} + +int mboot_pack_write(uint32_t addr, const uint8_t *src8, size_t len) { + if (addr == APPLICATION_ADDR) { + // Base address of main firmware, reset any previous state + firmware_chunk_base_addr = 0; + } + + if (firmware_chunk_base_addr == 0) { + // First piece of data starting a new chunk, so set the base address. + firmware_chunk_base_addr = addr; + } + + if (addr < firmware_chunk_base_addr) { + // Address out of range. + firmware_chunk_base_addr = 0; + return -1; + } + + size_t offset = addr - firmware_chunk_base_addr; + if (offset + len > sizeof(firmware_chunk_buf)) { + // Address/length out of range. + firmware_chunk_base_addr = 0; + return -1; + } + + // Copy in the new data piece into the chunk buffer. + memcpy((uint8_t *)&firmware_chunk_buf + offset, src8, len); + + if (offset + len < sizeof(firmware_chunk_buf.header)) { + // Don't have the header yet. + return 0; + } + + if (firmware_chunk_buf.header.header_vers != MBOOT_PACK_HEADER_VERSION) { + // Chunk header has the wrong version. + dfu_context.status = DFU_STATUS_ERROR_FILE; + dfu_context.error = MBOOT_ERROR_STR_INVALID_SIG_IDX; + return -1; + } + + if (firmware_chunk_buf.header.address != firmware_chunk_base_addr) { + // Chunk address doesn't agree with dfu address, abort. + dfu_context.status = DFU_STATUS_ERROR_ADDRESS; + dfu_context.error = MBOOT_ERROR_STR_INVALID_SIG_IDX; + return -1; + } + + if (offset + len < sizeof(firmware_chunk_buf.header) + firmware_chunk_buf.header.length + sizeof(firmware_chunk_buf.signature)) { + // Don't have the full chunk yet. + return 0; + } + + // Have the full chunk in firmware_chunk_buf, process it now. + + // Reset the chunk base address for the next chunk that comes in. + firmware_chunk_base_addr = 0; + + // Verify the signature of the chunk. + const size_t fw_len = firmware_chunk_buf.header.length; + const uint8_t *sig = &firmware_chunk_buf.data[0] + fw_len; + if (hydro_sign_verify(sig, &firmware_chunk_buf, sizeof(firmware_chunk_buf.header) + fw_len, + MBOOT_PACK_HYDRO_CONTEXT, mboot_pack_sign_public_key) != 0) { + // Signature failed + dfu_context.status = DFU_STATUS_ERROR_VERIFY; + dfu_context.error = MBOOT_ERROR_STR_INVALID_SIG_IDX; + return -1; + } + + // Signature passed, we have valid chunk. + + if (firmware_chunk_buf.header.format == MBOOT_PACK_CHUNK_META) { + // Ignore META chunks. + return 0; + } else if (firmware_chunk_buf.header.format == MBOOT_PACK_CHUNK_FULL_SIG) { + return mboot_pack_handle_full_sig(); + } else if (firmware_chunk_buf.header.format == MBOOT_PACK_CHUNK_FW_RAW + || firmware_chunk_buf.header.format == MBOOT_PACK_CHUNK_FW_GZIP) { + return mboot_pack_handle_firmware(); + } else { + // Unsupported contents. + return -1; + } +} + +#endif // MBOOT_ENABLE_PACKING diff --git a/ports/stm32/mboot/pack.h b/ports/stm32/mboot/pack.h new file mode 100644 index 0000000000..195f297ca1 --- /dev/null +++ b/ports/stm32/mboot/pack.h @@ -0,0 +1,82 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017-2019 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_MBOOT_PACK_H +#define MICROPY_INCLUDED_STM32_MBOOT_PACK_H + +#include +#include "py/mphal.h" + +#if MBOOT_ENABLE_PACKING + +#include "lib/libhydrogen/hydrogen.h" + +// Encrypted & signed bootloader support + +/******************************************************************************/ +// Interface + +#define MBOOT_PACK_HEADER_VERSION (1) + +// Used by libhydrogen for signing and secretbox context. +#define MBOOT_PACK_HYDRO_CONTEXT "mbootenc" + +// Maximum size of the firmware payload. +#define MBOOT_PACK_DFU_CHUNK_BUF_SIZE (MBOOT_PACK_CHUNKSIZE + hydro_secretbox_HEADERBYTES) + +enum mboot_pack_chunk_format { + MBOOT_PACK_CHUNK_META = 0, + MBOOT_PACK_CHUNK_FULL_SIG = 1, + MBOOT_PACK_CHUNK_FW_RAW = 2, + MBOOT_PACK_CHUNK_FW_GZIP = 3, +}; + +// Each DFU chunk transfered has this header to validate it. + +typedef struct _mboot_pack_chunk_buf_t { + struct { + uint8_t header_vers; + uint8_t format; // enum mboot_pack_chunk_format + uint8_t _pad[2]; + uint32_t address; + uint32_t length; // number of bytes in following "data" payload, excluding "signature" + } header; + uint8_t data[MBOOT_PACK_DFU_CHUNK_BUF_SIZE]; + uint8_t signature[hydro_sign_BYTES]; +} mboot_pack_chunk_buf_t; + +// Signing and encryption keys, stored in mboot flash, provided externally. +extern const uint8_t mboot_pack_sign_public_key[hydro_sign_PUBLICKEYBYTES]; +extern const uint8_t mboot_pack_secretbox_key[hydro_secretbox_KEYBYTES]; + +/******************************************************************************/ +// Implementation + +void mboot_pack_init(void); +int mboot_pack_write(uint32_t addr, const uint8_t *src8, size_t len); + +#endif // MBOOT_ENABLE_PACKING + +#endif // MICROPY_INCLUDED_STM32_MBOOT_PACK_H diff --git a/ports/stm32/mboot/stm32_generic.ld b/ports/stm32/mboot/stm32_generic.ld index 0504d6d6ae..ade4349674 100644 --- a/ports/stm32/mboot/stm32_generic.ld +++ b/ports/stm32/mboot/stm32_generic.ld @@ -6,11 +6,11 @@ MEMORY { FLASH_BL (rx) : ORIGIN = 0x08000000, LENGTH = 32K /* sector 0 (can be 32K) */ - RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 96K + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 120K } /* produce a link error if there is not this amount of RAM for these sections */ -_minimum_stack_size = 2K; +_minimum_stack_size = 8K; /* Define tho top end of the stack. The stack is full descending so begins just above last byte of RAM. Note that EABI requires the stack to be 8-byte diff --git a/ports/stm32/mboot/vfs_fat.c b/ports/stm32/mboot/vfs_fat.c index 20de074f0d..5120bdb104 100644 --- a/ports/stm32/mboot/vfs_fat.c +++ b/ports/stm32/mboot/vfs_fat.c @@ -42,7 +42,7 @@ DRESULT disk_read(void *pdrv, BYTE *buf, DWORD sector, UINT count) { vfs_fat_context_t *ctx = pdrv; if (0 <= sector && sector < ctx->bdev_byte_len / 512) { - do_read(ctx->bdev_base_addr + sector * SECSIZE, count * SECSIZE, buf); + hw_read(ctx->bdev_base_addr + sector * SECSIZE, count * SECSIZE, buf); return RES_OK; } diff --git a/ports/stm32/mboot/vfs_lfs.c b/ports/stm32/mboot/vfs_lfs.c index dec7c015fc..e4dc511db5 100644 --- a/ports/stm32/mboot/vfs_lfs.c +++ b/ports/stm32/mboot/vfs_lfs.c @@ -72,7 +72,7 @@ static uint8_t lfs_lookahead_buffer[LFS_LOOKAHEAD_SIZE]; static int dev_read(const struct LFSx_API (config) * c, LFSx_API(block_t) block, LFSx_API(off_t) off, void *buffer, LFSx_API(size_t) size) { VFS_LFSx_CONTEXT_T *ctx = c->context; if (0 <= block && block < ctx->config.block_count) { - do_read(ctx->bdev_base_addr + block * ctx->config.block_size + off, size, buffer); + hw_read(ctx->bdev_base_addr + block * ctx->config.block_size + off, size, buffer); return LFSx_MACRO(_ERR_OK); } return LFSx_MACRO(_ERR_IO); @@ -93,7 +93,7 @@ static int dev_sync(const struct LFSx_API (config) * c) { int VFS_LFSx_MOUNT(VFS_LFSx_CONTEXT_T *ctx, uint32_t base_addr, uint32_t byte_len) { // Read start of superblock. uint8_t buf[48]; - do_read(base_addr, sizeof(buf), buf); + hw_read(base_addr, sizeof(buf), buf); // Verify littlefs and detect block size. if (memcmp(&buf[SUPERBLOCK_MAGIC_OFFSET], "littlefs", 8) != 0) { diff --git a/ports/stm32/mpconfigport.mk b/ports/stm32/mpconfigport.mk index c6b3ddc74f..98ae93d20b 100644 --- a/ports/stm32/mpconfigport.mk +++ b/ports/stm32/mpconfigport.mk @@ -1,4 +1,4 @@ -# Enable/disable extra modules +# Enable/disable extra modules and features # wiznet5k module for ethernet support; valid values are: # 0 : no Wiznet support @@ -11,3 +11,8 @@ MICROPY_PY_CC3K ?= 0 # VFS FAT FS support MICROPY_VFS_FAT ?= 1 + +# Encrypted/signed bootloader support (ensure the MBOOT_PACK_xxx values match stm32/mboot/Makefile) +MBOOT_ENABLE_PACKING ?= 0 +MBOOT_PACK_CHUNKSIZE ?= 16384 +MBOOT_PACK_KEYS_FILE ?= $(BOARD_DIR)/mboot_keys.h From f6e6ef69e07d8779df6431e491012f49fb9afb44 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 15 Jan 2021 03:01:36 +1100 Subject: [PATCH 272/337] stm32/boards/NUCLEO_WB55: Enable MBOOT with packing mode. To have at least one board configured with MBOOT_ENABLE_PACKING, for CI testing purposes and demonstration of the feature. Signed-off-by: Damien George --- ports/stm32/boards/NUCLEO_WB55/mboot_keys.h | 5 +++++ ports/stm32/boards/NUCLEO_WB55/mpconfigboard.mk | 7 +++++++ 2 files changed, 12 insertions(+) create mode 100644 ports/stm32/boards/NUCLEO_WB55/mboot_keys.h diff --git a/ports/stm32/boards/NUCLEO_WB55/mboot_keys.h b/ports/stm32/boards/NUCLEO_WB55/mboot_keys.h new file mode 100644 index 0000000000..773813079a --- /dev/null +++ b/ports/stm32/boards/NUCLEO_WB55/mboot_keys.h @@ -0,0 +1,5 @@ +//const uint8_t mboot_pack_sign_secret_key[] = {0xf1,0xd8,0x66,0x08,0xbc,0x78,0x39,0x65,0x6a,0xf4,0x88,0xf4,0x43,0x4d,0x10,0xfe,0x4f,0x79,0xb9,0xc3,0x77,0x36,0x23,0xc8,0xcf,0x62,0xa1,0x90,0xf1,0xdc,0xd9,0xbc,0xb2,0x2e,0x59,0x1e,0x53,0x04,0x54,0xd5,0xd1,0x3a,0x6d,0x2e,0x79,0x3e,0xb5,0x70,0xb4,0x9f,0x33,0xff,0x90,0x1d,0xc3,0x54,0x90,0x12,0x96,0x79,0xf4,0xed,0x56,0x75}; +const uint8_t mboot_pack_sign_public_key[] = {0xb2,0x2e,0x59,0x1e,0x53,0x04,0x54,0xd5,0xd1,0x3a,0x6d,0x2e,0x79,0x3e,0xb5,0x70,0xb4,0x9f,0x33,0xff,0x90,0x1d,0xc3,0x54,0x90,0x12,0x96,0x79,0xf4,0xed,0x56,0x75}; +const uint8_t mboot_pack_secretbox_key[] = {0x4a,0xbe,0x9b,0xed,0x55,0x9f,0x74,0xeb,0x1e,0x70,0xde,0xf5,0x19,0x0a,0xeb,0xa5,0x68,0xea,0xb0,0x88,0xe6,0xfe,0x4d,0x10,0x69,0x23,0x06,0x7f,0xdd,0x83,0xf0,0xbf}; + +// The above keys are for demonstration purposes only, do not use them in production! diff --git a/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.mk b/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.mk index dcec788ed4..4cb047c95e 100644 --- a/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.mk +++ b/ports/stm32/boards/NUCLEO_WB55/mpconfigboard.mk @@ -1,3 +1,7 @@ +# By default this board is configured to use mboot with packing (signing and encryption) +# enabled. Mboot must be deployed first. +USE_MBOOT ?= 1 + MCU_SERIES = wb CMSIS_MCU = STM32WB55xx AF_FILE = boards/stm32wb55_af.csv @@ -17,3 +21,6 @@ endif MICROPY_PY_BLUETOOTH = 1 MICROPY_BLUETOOTH_NIMBLE = 1 MICROPY_VFS_LFS2 = 1 + +# Mboot settings +MBOOT_ENABLE_PACKING = 1 From de2374cdc6889401effba91287b0978a2a59089e Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 15 Jan 2021 15:17:12 +1100 Subject: [PATCH 273/337] tools/ci.sh: Pip install pyhy for stm32 builds. Signed-off-by: Damien George --- tools/ci.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/ci.sh b/tools/ci.sh index 1a413ec991..a68e6c113d 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -192,6 +192,7 @@ function ci_samd_build { function ci_stm32_setup { ci_gcc_arm_setup + pip3 install pyhy } function ci_stm32_pyb_build { From 49dd9ba1a5e50c400a3cfd604dd884bf9fea628c Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 18 Jan 2021 13:52:47 +1100 Subject: [PATCH 274/337] stm32/Makefile: Use MBOOT_PACK_KEYS_FILE as depedency of .pack.dfu. To match the definition of GENERATE_PACK_DFU, so a board can customise the location/name of this file if needed. Signed-off-by: Damien George --- ports/stm32/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index d8c9772b26..6e5fc15363 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -679,7 +679,7 @@ $(BUILD)/firmware.dfu: $(BUILD)/firmware0.bin $(BUILD)/firmware1.bin $(call GENERATE_DFU,$@,$(word 1,$^),$(TEXT0_ADDR),$(word 2,$^),$(TEXT1_ADDR)) endif -$(BUILD)/firmware.pack.dfu: $(BUILD)/firmware.dfu $(BOARD_DIR)/mboot_keys.h +$(BUILD)/firmware.pack.dfu: $(BUILD)/firmware.dfu $(MBOOT_PACK_KEYS_FILE) $(call GENERATE_PACK_DFU,$@,$<) $(BUILD)/firmware.hex: $(BUILD)/firmware.elf From aa136b4d78c7f81e63bde20ae17ab69bbbf456db Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 22 Jan 2021 17:30:25 +1100 Subject: [PATCH 275/337] extmod/modbluetooth: Add ble.hci_cmd(ogf, ocf, req, resp) function. This allows sending arbitrary HCI commands and getting the response. The return value of the function is the status of the command. This is intended for debugging and not to be a part of the public API, and must be enabled via mpconfigboard.h. It's currently only implemented for NimBLE bindings. Signed-off-by: Jim Mussared --- extmod/modbluetooth.c | 20 ++++++++++++++++++++ extmod/modbluetooth.h | 10 ++++++++++ extmod/nimble/modbluetooth_nimble.c | 23 ++++++++++++++++++++++- 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index caaf872e7d..8870a21c4d 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -884,6 +884,23 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_l2cap_recvinto_obj, 4, #endif // MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS +#if MICROPY_PY_BLUETOOTH_ENABLE_HCI_CMD + +STATIC mp_obj_t bluetooth_ble_hci_cmd(size_t n_args, const mp_obj_t *args) { + mp_int_t ogf = mp_obj_get_int(args[1]); + mp_int_t ocf = mp_obj_get_int(args[2]); + mp_buffer_info_t bufinfo_request = {0}; + mp_buffer_info_t bufinfo_response = {0}; + mp_get_buffer_raise(args[3], &bufinfo_request, MP_BUFFER_READ); + mp_get_buffer_raise(args[4], &bufinfo_response, MP_BUFFER_WRITE); + uint8_t status = 0; + bluetooth_handle_errno(mp_bluetooth_hci_cmd(ogf, ocf, bufinfo_request.buf, bufinfo_request.len, bufinfo_response.buf, bufinfo_response.len, &status)); + return MP_OBJ_NEW_SMALL_INT(status); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_hci_cmd_obj, 5, 5, bluetooth_ble_hci_cmd); + +#endif // MICROPY_PY_BLUETOOTH_ENABLE_HCI_CMD + // ---------------------------------------------------------------------------- // Bluetooth object: Definition // ---------------------------------------------------------------------------- @@ -927,6 +944,9 @@ STATIC const mp_rom_map_elem_t bluetooth_ble_locals_dict_table[] = { { 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 + #if MICROPY_PY_BLUETOOTH_ENABLE_HCI_CMD + { MP_ROM_QSTR(MP_QSTR_hci_cmd), MP_ROM_PTR(&bluetooth_ble_hci_cmd_obj) }, + #endif }; STATIC MP_DEFINE_CONST_DICT(bluetooth_ble_locals_dict, bluetooth_ble_locals_dict_table); diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index f14033de30..c96427fcb4 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -60,6 +60,12 @@ #define MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING (0) #endif +// Optionally enable support for the `hci_cmd` function allowing +// Python to directly low-level HCI commands. +#ifndef MICROPY_PY_BLUETOOTH_ENABLE_HCI_CMD +#define MICROPY_PY_BLUETOOTH_ENABLE_HCI_CMD (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 @@ -387,6 +393,10 @@ int mp_bluetooth_l2cap_send(uint16_t conn_handle, uint16_t cid, const uint8_t *b 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 +#if MICROPY_PY_BLUETOOTH_ENABLE_HCI_CMD +int mp_bluetooth_hci_cmd(uint16_t ogf, uint16_t ocf, const uint8_t *req, size_t req_len, uint8_t *resp, size_t resp_len, uint8_t *status); +#endif // MICROPY_PY_BLUETOOTH_ENABLE_HCI_CMD + ///////////////////////////////////////////////////////////////////////////// // API implemented by modbluetooth (called by port-specific implementations): diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 9ceeab7d7b..8bead890d8 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -81,9 +81,11 @@ STATIC int ble_hs_err_to_errno(int err) { return 0; } if (err >= 0 && (unsigned)err < MP_ARRAY_SIZE(ble_hs_err_to_errno_table) && ble_hs_err_to_errno_table[err]) { + // Return an MP_Exxx error code. return ble_hs_err_to_errno_table[err]; } else { - return MP_EIO; + // Pass through the BLE error code. + return -err; } } @@ -1660,6 +1662,25 @@ 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_HCI_CMD + +// For ble_hs_hci_cmd_tx +#include "nimble/host/src/ble_hs_hci_priv.h" + +int mp_bluetooth_hci_cmd(uint16_t ogf, uint16_t ocf, const uint8_t *req, size_t req_len, uint8_t *resp, size_t resp_len, uint8_t *status) { + int rc = ble_hs_hci_cmd_tx(BLE_HCI_OP(ogf, ocf), req, req_len, resp, resp_len); + if (rc < BLE_HS_ERR_HCI_BASE || rc >= BLE_HS_ERR_HCI_BASE + 0x100) { + // The controller didn't handle the command (e.g. HCI timeout). + return ble_hs_err_to_errno(rc); + } else { + // The command executed, but had an error (i.e. invalid parameter). + *status = rc - BLE_HS_ERR_HCI_BASE; + return 0; + } +} + +#endif // MICROPY_PY_BLUETOOTH_ENABLE_HCI_CMD + #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) { From 063d7cc0e2f076bd4299ab487563d398c3d212b6 Mon Sep 17 00:00:00 2001 From: Yonatan Schachter Date: Tue, 24 Nov 2020 16:59:04 +0200 Subject: [PATCH 276/337] zephyr: Add basic UART functionality to machine module. Currently supports only polling read and write. Signed-off-by: Yonatan Schachter --- ports/zephyr/Makefile | 1 + ports/zephyr/machine_uart.c | 168 ++++++++++++++++++++++++++++++++++++ ports/zephyr/modmachine.c | 1 + ports/zephyr/modmachine.h | 1 + 4 files changed, 171 insertions(+) create mode 100644 ports/zephyr/machine_uart.c diff --git a/ports/zephyr/Makefile b/ports/zephyr/Makefile index 169aebc267..c200062a3f 100644 --- a/ports/zephyr/Makefile +++ b/ports/zephyr/Makefile @@ -49,6 +49,7 @@ SRC_C = main.c \ modmachine.c \ machine_i2c.c \ machine_pin.c \ + machine_uart.c \ uart_core.c \ zephyr_storage.c \ lib/timeutils/timeutils.c \ diff --git a/ports/zephyr/machine_uart.c b/ports/zephyr/machine_uart.c new file mode 100644 index 0000000000..6ae8707d7f --- /dev/null +++ b/ports/zephyr/machine_uart.c @@ -0,0 +1,168 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George + * Copyright (c) 2020 Yonatan Schachter + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include + +#include +#include + +#include "py/runtime.h" +#include "py/stream.h" +#include "py/mperrno.h" +#include "py/objstr.h" +#include "modmachine.h" + +typedef struct _machine_uart_obj_t { + mp_obj_base_t base; + const struct device *dev; + uint16_t timeout; // timeout waiting for first char (in ms) + uint16_t timeout_char; // timeout waiting between chars (in ms) +} machine_uart_obj_t; + +STATIC const char *_parity_name[] = {"None", "Odd", "Even", "Mark", "Space"}; +STATIC const char *_stop_bits_name[] = {"0.5", "1", "1.5", "2"}; +STATIC const char *_data_bits_name[] = {"5", "6", "7", "8", "9"}; +STATIC const char *_flow_control_name[] = {"None", "RTS/CTS", "DTR/DSR"}; + +STATIC void machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + struct uart_config config; + uart_config_get(self->dev, &config); + mp_printf(print, "UART(\"%s\", baudrate=%u, data_bits=%s, parity_bits=%s, stop_bits=%s, flow_control=%s, timeout=%u, timeout_char=%u)", + self->dev->name, config.baudrate, _data_bits_name[config.data_bits], + _parity_name[config.parity], _stop_bits_name[config.stop_bits], _flow_control_name[config.flow_ctrl], + self->timeout, self->timeout_char); +} + +STATIC void machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_timeout, ARG_timeout_char }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_timeout_char, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_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); + + self->timeout = args[ARG_timeout].u_int; + self->timeout_char = args[ARG_timeout_char].u_int; +} + +STATIC mp_obj_t machine_uart_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + GET_STR_DATA_LEN(args[0], name, name_len); + + machine_uart_obj_t *self = m_new_obj(machine_uart_obj_t); + self->base.type = &machine_uart_type; + self->dev = device_get_binding(name); + if (!self->dev) { + mp_raise_ValueError(MP_ERROR_TEXT("Bad device name")); + } + + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + machine_uart_init_helper(self, n_args - 1, args + 1, &kw_args); + + return MP_OBJ_FROM_PTR(self); +} + +STATIC const mp_rom_map_elem_t machine_uart_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(machine_uart_locals_dict, machine_uart_locals_dict_table); + +STATIC mp_uint_t machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + uint8_t *buffer = (uint8_t *)buf_in; + uint8_t data; + mp_uint_t bytes_read = 0; + size_t elapsed_ms = 0; + size_t time_to_wait = self->timeout; + + while ((elapsed_ms < time_to_wait) && (bytes_read < size)) { + if (!uart_poll_in(self->dev, &data)) { + buffer[bytes_read++] = data; + elapsed_ms = 0; + time_to_wait = self->timeout_char; + } else { + k_msleep(1); + elapsed_ms++; + } + } + return bytes_read; +} + +STATIC mp_uint_t machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + uint8_t *buffer = (uint8_t *)buf_in; + + for (mp_uint_t i = 0; i < size; i++) { + uart_poll_out(self->dev, buffer[i]); + } + + return size; +} + +STATIC mp_uint_t machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, mp_uint_t arg, int *errcode) { + mp_uint_t ret; + + if (request == MP_STREAM_POLL) { + ret = 0; + // read is always blocking + + if (arg & MP_STREAM_POLL_WR) { + ret |= MP_STREAM_POLL_WR; + } + return ret; + } else { + *errcode = MP_EINVAL; + ret = MP_STREAM_ERROR; + } + return ret; +} + +STATIC const mp_stream_p_t uart_stream_p = { + .read = machine_uart_read, + .write = machine_uart_write, + .ioctl = machine_uart_ioctl, + .is_text = false, +}; + +const mp_obj_type_t machine_uart_type = { + { &mp_type_type }, + .name = MP_QSTR_UART, + .print = machine_uart_print, + .make_new = machine_uart_make_new, + .getiter = mp_identity_getiter, + .iternext = mp_stream_unbuffered_iter, + .protocol = &uart_stream_p, + .locals_dict = (mp_obj_dict_t *)&machine_uart_locals_dict, +}; diff --git a/ports/zephyr/modmachine.c b/ports/zephyr/modmachine.c index 968f758b93..107895bea0 100644 --- a/ports/zephyr/modmachine.c +++ b/ports/zephyr/modmachine.c @@ -63,6 +63,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { #if MICROPY_PY_MACHINE_I2C { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_hard_i2c_type) }, #endif + { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&machine_uart_type) }, { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&machine_pin_type) }, { MP_ROM_QSTR(MP_QSTR_Signal), MP_ROM_PTR(&machine_signal_type) }, diff --git a/ports/zephyr/modmachine.h b/ports/zephyr/modmachine.h index b67da55338..957f2dd4f9 100644 --- a/ports/zephyr/modmachine.h +++ b/ports/zephyr/modmachine.h @@ -5,6 +5,7 @@ extern const mp_obj_type_t machine_pin_type; extern const mp_obj_type_t machine_hard_i2c_type; +extern const mp_obj_type_t machine_uart_type; MP_DECLARE_CONST_FUN_OBJ_0(machine_info_obj); From 769e822f190b2ae3c67a6612b7141a54920a19bf Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Fri, 11 Dec 2020 16:59:40 -0800 Subject: [PATCH 277/337] esp32/modnetwork: Synchronize WiFi AUTH_xxx constants with IDF values. --- ports/esp32/modnetwork.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/esp32/modnetwork.c b/ports/esp32/modnetwork.c index 4426335040..64f1c91dc7 100644 --- a/ports/esp32/modnetwork.c +++ b/ports/esp32/modnetwork.c @@ -772,6 +772,11 @@ STATIC const mp_rom_map_elem_t mp_module_network_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_AUTH_WPA_PSK), MP_ROM_INT(WIFI_AUTH_WPA_PSK) }, { MP_ROM_QSTR(MP_QSTR_AUTH_WPA2_PSK), MP_ROM_INT(WIFI_AUTH_WPA2_PSK) }, { MP_ROM_QSTR(MP_QSTR_AUTH_WPA_WPA2_PSK), MP_ROM_INT(WIFI_AUTH_WPA_WPA2_PSK) }, + { MP_ROM_QSTR(MP_QSTR_AUTH_WPA2_ENTERPRISE), MP_ROM_INT(WIFI_AUTH_WPA2_ENTERPRISE) }, + #if 0 // TODO: Remove this #if/#endif when lastest ISP IDF will be used + { MP_ROM_QSTR(MP_QSTR_AUTH_WPA3_PSK), MP_ROM_INT(WIFI_AUTH_WPA3_PSK) }, + { MP_ROM_QSTR(MP_QSTR_AUTH_WPA2_WPA3_PSK), MP_ROM_INT(WIFI_AUTH_WPA2_WPA3_PSK) }, + #endif { MP_ROM_QSTR(MP_QSTR_AUTH_MAX), MP_ROM_INT(WIFI_AUTH_MAX) }, { MP_ROM_QSTR(MP_QSTR_PHY_LAN8720), MP_ROM_INT(PHY_LAN8720) }, From 419134bea47953888344d325220c376117f6b2b4 Mon Sep 17 00:00:00 2001 From: Oliver Joos Date: Wed, 16 Dec 2020 21:25:18 +0100 Subject: [PATCH 278/337] tests/extmod: Add test for the precision of utime functions. According to documentation time() has a precision of at least 1 second. This test runs for 2.5 seconds and calls all utime functions every 100ms. Then it checks if they returned enough different results. All functions with sub-second precision will return ~25 results. This test passes with 15 results or more. Functions that do not exist are skipped silently. --- tests/extmod/utime_res.py | 65 +++++++++++++++++++++++++++++++++++ tests/extmod/utime_res.py.exp | 9 +++++ 2 files changed, 74 insertions(+) create mode 100644 tests/extmod/utime_res.py create mode 100644 tests/extmod/utime_res.py.exp diff --git a/tests/extmod/utime_res.py b/tests/extmod/utime_res.py new file mode 100644 index 0000000000..4b62433483 --- /dev/null +++ b/tests/extmod/utime_res.py @@ -0,0 +1,65 @@ +# test utime resolutions + +try: + import utime +except ImportError: + print("SKIP") + raise SystemExit + + +def gmtime_time(): + return utime.gmtime(utime.time()) + + +def localtime_time(): + return utime.localtime(utime.time()) + + +def test(): + TEST_TIME = 2500 + EXPECTED_MAP = ( + # (function name, min. number of results in 2.5 sec) + ("time", 3), + ("gmtime", 3), + ("localtime", 3), + ("gmtime_time", 3), + ("localtime_time", 3), + ("ticks_ms", 15), + ("ticks_us", 15), + ("ticks_ns", 15), + ("ticks_cpu", 15), + ) + + # call time functions + results_map = {} + end_time = utime.ticks_ms() + TEST_TIME + while utime.ticks_diff(end_time, utime.ticks_ms()) > 0: + utime.sleep_ms(100) + for func_name, _ in EXPECTED_MAP: + try: + time_func = getattr(utime, func_name, None) or globals()[func_name] + now = time_func() # may raise AttributeError + except (KeyError, AttributeError): + continue + try: + results_map[func_name].add(now) + except KeyError: + results_map[func_name] = {now} + + # check results + for func_name, min_len in EXPECTED_MAP: + print("Testing %s" % func_name) + results = results_map.get(func_name) + if results is None: + pass + elif func_name == "ticks_cpu" and results == {0}: + # ticks_cpu() returns 0 on some ports (e.g. unix) + pass + elif len(results) < min_len: + print( + "%s() returns %s result%s in %s ms, expecting >= %s" + % (func_name, len(results), "s"[: len(results) != 1], TEST_TIME, min_len) + ) + + +test() diff --git a/tests/extmod/utime_res.py.exp b/tests/extmod/utime_res.py.exp new file mode 100644 index 0000000000..08c2d82950 --- /dev/null +++ b/tests/extmod/utime_res.py.exp @@ -0,0 +1,9 @@ +Testing time +Testing gmtime +Testing localtime +Testing gmtime_time +Testing localtime_time +Testing ticks_ms +Testing ticks_us +Testing ticks_ns +Testing ticks_cpu From 290dc1d5eeba005bdc604a9eb2d781a922081c29 Mon Sep 17 00:00:00 2001 From: Oliver Joos Date: Wed, 16 Dec 2020 22:35:52 +0100 Subject: [PATCH 279/337] unix/modtime: Fix time() precision on unix ports with non-double floats. With MICROPY_FLOAT_IMPL_FLOAT the results of utime.time(), gmtime() and localtime() change only every 129 seconds. As one consequence tests/extmod/vfs_lfs_mtime.py will fail on a unix port with LFS support. With this patch these functions only return floats if MICROPY_FLOAT_IMPL_DOUBLE is used. Otherwise they return integers. --- ports/unix/modtime.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/unix/modtime.c b/ports/unix/modtime.c index e08409e20e..91c0a19413 100644 --- a/ports/unix/modtime.c +++ b/ports/unix/modtime.c @@ -67,7 +67,7 @@ static inline int msec_sleep_tv(struct timeval *tv) { #endif STATIC mp_obj_t mod_time_time(void) { - #if MICROPY_PY_BUILTINS_FLOAT + #if MICROPY_PY_BUILTINS_FLOAT && MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE struct timeval tv; gettimeofday(&tv, NULL); mp_float_t val = tv.tv_sec + (mp_float_t)tv.tv_usec / 1000000; @@ -137,7 +137,7 @@ STATIC mp_obj_t mod_time_gm_local_time(size_t n_args, const mp_obj_t *args, stru if (n_args == 0) { t = time(NULL); } else { - #if MICROPY_PY_BUILTINS_FLOAT + #if MICROPY_PY_BUILTINS_FLOAT && MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE mp_float_t val = mp_obj_get_float(args[0]); t = (time_t)MICROPY_FLOAT_C_FUN(trunc)(val); #else From 342dc617842c7ebdc2b93dcbbfe9f0f9bb4789d3 Mon Sep 17 00:00:00 2001 From: Vincent Duvert Date: Mon, 21 Dec 2020 18:48:53 +0100 Subject: [PATCH 280/337] cc3200/ftp: Add quotes to PWD response and allow FEAT prior to login. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit improves some FTP implementation details for better compatibility with FTP clients: * The PWD command now puts quotes around the directory name before returning it. This fixes BBEdit’s FTP client, which performs a PWD after each CWD and gets confused if the returned directory path is not surrounded by quotes. * The FEAT command is now allowed before logging in. This fixes the lftp client, which send FEAT first and gets confused (tries to use TLS) if the server responds with 332. --- ports/cc3200/ftp/ftp.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ports/cc3200/ftp/ftp.c b/ports/cc3200/ftp/ftp.c index ee80e51f52..37680bc939 100644 --- a/ports/cc3200/ftp/ftp.c +++ b/ports/cc3200/ftp/ftp.c @@ -684,7 +684,7 @@ static void ftp_process_cmd (void) { if (E_FTP_RESULT_OK == (result = ftp_recv_non_blocking(ftp_data.c_sd, ftp_cmd_buffer, FTP_MAX_PARAM_SIZE + FTP_CMD_SIZE_MAX, &len))) { // bufptr is moved as commands are being popped ftp_cmd_index_t cmd = ftp_pop_command(&bufptr); - if (!ftp_data.loggin.passvalid && (cmd != E_FTP_CMD_USER && cmd != E_FTP_CMD_PASS && cmd != E_FTP_CMD_QUIT)) { + if (!ftp_data.loggin.passvalid && (cmd != E_FTP_CMD_USER && cmd != E_FTP_CMD_PASS && cmd != E_FTP_CMD_QUIT && cmd != E_FTP_CMD_FEAT)) { ftp_send_reply(332, NULL); return; } @@ -718,7 +718,8 @@ static void ftp_process_cmd (void) { break; case E_FTP_CMD_PWD: case E_FTP_CMD_XPWD: - ftp_send_reply(257, ftp_path); + snprintf((char *)ftp_data.dBuffer, FTP_BUFFER_SIZE, "\"%s\"", ftp_path); + ftp_send_reply(257, (char *)ftp_data.dBuffer); break; case E_FTP_CMD_SIZE: { From 45f0b6ab6300e75a17f6427ec45d812e072a9522 Mon Sep 17 00:00:00 2001 From: Vincent Duvert Date: Wed, 23 Dec 2020 13:41:56 +0100 Subject: [PATCH 281/337] cc3200: Fix debug build. * Fix a typo in the Makefile that prevented the debug build to be actually enabled when BTYPE=debug is used. * Add a missing header in modmachine.c that is used when a debug build is created. --- ports/cc3200/application.mk | 2 +- ports/cc3200/mods/modmachine.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/cc3200/application.mk b/ports/cc3200/application.mk index b216ca16dd..2125274ae1 100644 --- a/ports/cc3200/application.mk +++ b/ports/cc3200/application.mk @@ -182,7 +182,7 @@ ifeq ($(BTYPE), release) CFLAGS += -DNDEBUG else ifeq ($(BTYPE), debug) -CFLAGS += -DNDEBUG +CFLAGS += -DDEBUG else $(error Invalid BTYPE specified) endif diff --git a/ports/cc3200/mods/modmachine.c b/ports/cc3200/mods/modmachine.c index ddecd47f2b..89800810c7 100644 --- a/ports/cc3200/mods/modmachine.c +++ b/ports/cc3200/mods/modmachine.c @@ -26,6 +26,7 @@ */ #include +#include #include "py/runtime.h" #include "py/mphal.h" From 0a079155e46d588095e4419b1095fd4ae691ab50 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 23 Jan 2021 17:15:18 +1100 Subject: [PATCH 282/337] github/workflows: Fix code-size CI workflow. Changes are: - Use ubuntu-20.04 so that gcc-multilib installs without error. - Use "fetch-depth: 100" to get history prior to pull request. Signed-off-by: Damien George --- .github/workflows/code_size.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/code_size.yml b/.github/workflows/code_size.yml index dd759a4f3f..7570261e7d 100644 --- a/.github/workflows/code_size.yml +++ b/.github/workflows/code_size.yml @@ -14,9 +14,11 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 + with: + fetch-depth: 100 - name: Install packages run: source tools/ci.sh && ci_code_size_setup - name: Build From 203e1d2a65273db3f6ff063ba1124a89c3482c0f Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 24 Jan 2021 15:02:20 +1100 Subject: [PATCH 283/337] tools/ci.sh: For code size build, fetch history of master branch only. It's not necessary to fetch all branches. Signed-off-by: Damien George --- tools/ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci.sh b/tools/ci.sh index a68e6c113d..86c17bb691 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -42,7 +42,7 @@ 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 + git fetch --depth=100 upstream master # 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` From 4eaebc1988699db6ebfd35fbe56a3e8d4cd0b373 Mon Sep 17 00:00:00 2001 From: nanjekyejoannah Date: Mon, 12 Oct 2020 17:25:05 -0300 Subject: [PATCH 284/337] docs/develop: Add MicroPython Internals chapter. This commit adds many new sections to the existing "Developing and building MicroPython" chapter to make it all about the internals of MicroPython. This work was done as part of Google's Season of Docs 2020. --- docs/develop/compiler.rst | 317 +++++++++++++++++++++++++ docs/develop/extendingmicropython.rst | 19 ++ docs/develop/gettingstarted.rst | 324 ++++++++++++++++++++++++++ docs/develop/img/bitmap.png | Bin 0 -> 6388 bytes docs/develop/img/collision.png | Bin 0 -> 3464 bytes docs/develop/img/linprob.png | Bin 0 -> 3655 bytes docs/develop/index.rst | 31 ++- docs/develop/library.rst | 86 +++++++ docs/develop/maps.rst | 63 +++++ docs/develop/memorymgt.rst | 141 +++++++++++ docs/develop/optimizations.rst | 72 ++++++ docs/develop/porting.rst | 310 ++++++++++++++++++++++++ docs/develop/publiccapi.rst | 25 ++ docs/develop/qstr.rst | 7 +- docs/develop/writingtests.rst | 70 ++++++ 15 files changed, 1454 insertions(+), 11 deletions(-) create mode 100644 docs/develop/compiler.rst create mode 100644 docs/develop/extendingmicropython.rst create mode 100644 docs/develop/gettingstarted.rst create mode 100644 docs/develop/img/bitmap.png create mode 100644 docs/develop/img/collision.png create mode 100644 docs/develop/img/linprob.png create mode 100644 docs/develop/library.rst create mode 100644 docs/develop/maps.rst create mode 100644 docs/develop/memorymgt.rst create mode 100644 docs/develop/optimizations.rst create mode 100644 docs/develop/porting.rst create mode 100644 docs/develop/publiccapi.rst create mode 100644 docs/develop/writingtests.rst diff --git a/docs/develop/compiler.rst b/docs/develop/compiler.rst new file mode 100644 index 0000000000..2007657490 --- /dev/null +++ b/docs/develop/compiler.rst @@ -0,0 +1,317 @@ +.. _compiler: + +The Compiler +============ + +The compilation process in MicroPython involves the following steps: + +* The lexer converts the stream of text that makes up a MicroPython program into tokens. +* The parser then converts the tokens into an abstract syntax (parse tree). +* Then bytecode or native code is emitted based on the parse tree. + +For purposes of this discussion we are going to add a simple language feature ``add1`` +that can be use in Python as: + +.. code-block:: bash + + >>> add1 3 + 4 + >>> + +The ``add1`` statement takes an integer as argument and adds ``1`` to it. + +Adding a grammar rule +---------------------- + +MicroPython's grammar is based on the `CPython grammar `_ +and is defined in `py/grammar.h `_. +This grammar is what is used to parse MicroPython source files. + +There are two macros you need to know to define a grammar rule: ``DEF_RULE`` and ``DEF_RULE_NC``. +``DEF_RULE`` allows you to define a rule with an associated compile function, +while ``DEF_RULE_NC`` has no compile (NC) function for it. + +A simple grammar definition with a compile function for our new ``add1`` statement +looks like the following: + +.. code-block:: c + + DEF_RULE(add1_stmt, c(add1_stmt), and(2), tok(KW_ADD1), rule(testlist)) + +The second argument ``c(add1_stmt)`` is the corresponding compile function that should be implemented +in ``py/compile.c`` to turn this rule into executable code. + +The third required argument can be ``or`` or ``and``. This specifies the number of nodes associated +with a statement. For example, in this case, our ``add1`` statement is similar to ADD1 in assembly +language. It takes one numeric argument. Therefore, the ``add1_stmt`` has two nodes associated with it. +One node is for the statement itself, i.e the literal ``add1`` corresponding to ``KW_ADD1``, +and the other for its argument, a ``testlist`` rule which is the top-level expression rule. + +.. note:: + The ``add1`` rule here is just an example and not part of the standard + MicroPython grammar. + +The fourth argument in this example is the token associated with the rule, ``KW_ADD1``. This token should be +defined in the lexer by editing ``py/lexer.h``. + +Defining the same rule without a compile function is achieved by using the ``DEF_RULE_NC`` macro +and omitting the compile function argument: + +.. code-block:: c + + DEF_RULE_NC(add1_stmt, and(2), tok(KW_ADD1), rule(testlist)) + +The remaining arguments take on the same meaning. A rule without a compile function must +be handled explicitly by all rules that may have this rule as a node. Such NC-rules are usually +used to express sub-parts of a complicated grammar structure that cannot be expressed in a +single rule. + +.. note:: + The macros ``DEF_RULE`` and ``DEF_RULE_NC`` take other arguments. For an in-depth understanding of + supported parameters, see `py/grammar.h `_. + +Adding a lexical token +---------------------- + +Every rule defined in the grammar should have a token associated with it that is defined in ``py/lexer.h``. +Add this token by editing the ``_mp_token_kind_t`` enum: + +.. code-block:: c + :emphasize-lines: 12 + + typedef enum _mp_token_kind_t { + ... + MP_TOKEN_KW_OR, + MP_TOKEN_KW_PASS, + MP_TOKEN_KW_RAISE, + MP_TOKEN_KW_RETURN, + MP_TOKEN_KW_TRY, + MP_TOKEN_KW_WHILE, + MP_TOKEN_KW_WITH, + MP_TOKEN_KW_YIELD, + MP_TOKEN_KW_ADD1, + ... + } mp_token_kind_t; + +Then also edit ``py/lexer.c`` to add the new keyword literal text: + +.. code-block:: c + :emphasize-lines: 12 + + STATIC const char *const tok_kw[] = { + ... + "or", + "pass", + "raise", + "return", + "try", + "while", + "with", + "yield", + "add1", + ... + }; + +Notice the keyword is named depending on what you want it to be. For consistency, maintain the +naming standard accordingly. + +.. note:: + The order of these keywords in ``py/lexer.c`` must match the order of tokens in the enum + defined in ``py/lexer.h``. + +Parsing +------- + +In the parsing stage the parser takes the tokens produced by the lexer and converts them to an abstract syntax tree (AST) or +*parse tree*. The implementation for the parser is defined in `py/parse.c `_. + +The parser also maintains a table of constants for use in different aspects of parsing, similar to what a +`symbol table `_ +does. + +Several optimizations like `constant folding `_ +on integers for most operations e.g. logical, binary, unary, etc, and optimizing enhancements on parenthesis +around expressions are performed during this phase, along with some optimizations on strings. + +It's worth noting that *docstrings* are discarded and not accessible to the compiler. +Even optimizations like `string interning `_ are +not applied to *docstrings*. + +Compiler passes +--------------- + +Like many compilers, MicroPython compiles all code to MicroPython bytecode or native code. The functionality +that achieves this is implemented in `py/compile.c `_. +The most relevant method you should know about is this: + +.. code-block:: c + + mp_obj_t mp_compile(mp_parse_tree_t *parse_tree, qstr source_file, bool is_repl) { + // Compile the input parse_tree to a raw-code structure. + mp_raw_code_t *rc = mp_compile_to_raw_code(parse_tree, source_file, is_repl); + // Create and return a function object that executes the outer module. + return mp_make_function_from_raw_code(rc, MP_OBJ_NULL, MP_OBJ_NULL); + } + +The compiler compiles the code in four passes: scope, stack size, code size and emit. +Each pass runs the same C code over the same AST data structure, with different things +being computed each time based on the results of the previous pass. + +First pass +~~~~~~~~~~ + +In the first pass, the compiler learns about the known identifiers (variables) and +their scope, being global, local, closed over, etc. In the same pass the emitter +(bytecode or native code) also computes the number of labels needed for the emitted +code. + +.. code-block:: c + + // Compile pass 1. + comp->emit = emit_bc; + comp->emit_method_table = &emit_bc_method_table; + + uint max_num_labels = 0; + for (scope_t *s = comp->scope_head; s != NULL && comp->compile_error == MP_OBJ_NULL; s = s->next) { + if (s->emit_options == MP_EMIT_OPT_ASM) { + compile_scope_inline_asm(comp, s, MP_PASS_SCOPE); + } else { + compile_scope(comp, s, MP_PASS_SCOPE); + + // Check if any implicitly declared variables should be closed over. + for (size_t i = 0; i < s->id_info_len; ++i) { + id_info_t *id = &s->id_info[i]; + if (id->kind == ID_INFO_KIND_GLOBAL_IMPLICIT) { + scope_check_to_close_over(s, id); + } + } + } + ... + } + +Second and third passes +~~~~~~~~~~~~~~~~~~~~~~~ + +The second and third passes involve computing the Python stack size and code size +for the bytecode or native code. After the third pass the code size cannot change, +otherwise jump labels will be incorrect. + +.. code-block:: c + + for (scope_t *s = comp->scope_head; s != NULL && comp->compile_error == MP_OBJ_NULL; s = s->next) { + ... + + // Pass 2: Compute the Python stack size. + compile_scope(comp, s, MP_PASS_STACK_SIZE); + + // Pass 3: Compute the code size. + if (comp->compile_error == MP_OBJ_NULL) { + compile_scope(comp, s, MP_PASS_CODE_SIZE); + } + + ... + } + +Just before pass two there is a selection for the type of code to be emitted, which can +either be native or bytecode. + +.. code-block:: c + + // Choose the emitter type. + switch (s->emit_options) { + case MP_EMIT_OPT_NATIVE_PYTHON: + case MP_EMIT_OPT_VIPER: + if (emit_native == NULL) { + emit_native = NATIVE_EMITTER(new)(&comp->compile_error, &comp->next_label, max_num_labels); + } + comp->emit_method_table = NATIVE_EMITTER_TABLE; + comp->emit = emit_native; + break; + + default: + comp->emit = emit_bc; + comp->emit_method_table = &emit_bc_method_table; + break; + } + +The bytecode option is the default but something unique to note for the native +code option is that there is another option via ``VIPER``. See the +:ref:`Emitting native code ` section for more details on +viper annotations. + +There is also support for *inline assembly code*, where assembly instructions are +written as Python function calls but are emitted directly as the corresponding +machine code. This assembler has only three passes (scope, code size, emit) +and uses a different implementation, not the ``compile_scope`` function. +See the `inline assembler tutorial `_ +for more details. + +Fourth pass +~~~~~~~~~~~ + +The fourth pass emits the final code that can be executed, either bytecode in +the virtual machine, or native code directly by the CPU. + +.. code-block:: c + + for (scope_t *s = comp->scope_head; s != NULL && comp->compile_error == MP_OBJ_NULL; s = s->next) { + ... + + // Pass 4: Emit the compiled bytecode or native code. + if (comp->compile_error == MP_OBJ_NULL) { + compile_scope(comp, s, MP_PASS_EMIT); + } + } + +Emitting bytecode +----------------- + +Statements in Python code usually correspond to emitted bytecode, for example ``a + b`` +generates "push a" then "push b" then "binary op add". Some statements do not emit +anything but instead affect other things like the scope of variables, for example +``global a``. + +The implementation of a function that emits bytecode looks similar to this: + +.. code-block:: c + + void mp_emit_bc_unary_op(emit_t *emit, mp_unary_op_t op) { + emit_write_bytecode_byte(emit, 0, MP_BC_UNARY_OP_MULTI + op); + } + +We use the unary operator expressions for an example here but the implementation +details are similar for other statements/expressions. The method ``emit_write_bytecode_byte()`` +is a wrapper around the main function ``emit_get_cur_to_write_bytecode()`` that all +functions must call to emit bytecode. + +.. _emitting_native_code: + +Emitting native code +--------------------- + +Similar to how bytecode is generated, there should be a corresponding function in ``py/emitnative.c`` for each +code statement: + +.. code-block:: c + + STATIC void emit_native_unary_op(emit_t *emit, mp_unary_op_t op) { + vtype_kind_t vtype; + emit_pre_pop_reg(emit, &vtype, REG_ARG_2); + if (vtype == VTYPE_PYOBJ) { + emit_call_with_imm_arg(emit, MP_F_UNARY_OP, op, REG_ARG_1); + emit_post_push_reg(emit, VTYPE_PYOBJ, REG_RET); + } else { + adjust_stack(emit, 1); + EMIT_NATIVE_VIPER_TYPE_ERROR(emit, + MP_ERROR_TEXT("unary op %q not implemented"), mp_unary_op_method_name[op]); + } + } + +The difference here is that we have to handle *viper typing*. Viper annotations allow +us to handle more than one type of variable. By default all variables are Python objects, +but with viper a variable can also be declared as a machine-typed variable like a native +integer or pointer. Viper can be thought of as a superset of Python, where normal Python +objects are handled as usual, while native machine variables are handled in an optimised +way by using direct machine instructions for the operations. Viper typing may break +Python equivalence because, for example, integers become native integers and can overflow +(unlike Python integers which extend automatically to arbitrary precision). diff --git a/docs/develop/extendingmicropython.rst b/docs/develop/extendingmicropython.rst new file mode 100644 index 0000000000..7fb1ae47a0 --- /dev/null +++ b/docs/develop/extendingmicropython.rst @@ -0,0 +1,19 @@ +.. _extendingmicropython: + +Extending MicroPython in C +========================== + +This chapter describes options for implementing additional functionality in C, but from code +written outside of the main MicroPython repository. The first approach is useful for building +your own custom firmware with some project-specific additional modules or functions that can +be accessed from Python. The second approach is for building modules that can be loaded at runtime. + +Please see the :ref:`library section ` for more information on building core modules that +live in the main MicroPython repository. + +.. toctree:: + :maxdepth: 3 + + cmodules.rst + natmod.rst + \ No newline at end of file diff --git a/docs/develop/gettingstarted.rst b/docs/develop/gettingstarted.rst new file mode 100644 index 0000000000..3dd00a579a --- /dev/null +++ b/docs/develop/gettingstarted.rst @@ -0,0 +1,324 @@ +.. _gettingstarted: + +Getting Started +=============== + +This guide covers a step-by-step process on setting up version control, obtaining and building +a copy of the source code for a port, building the documentation, running tests, and a description of the +directory structure of the MicroPython code base. + +Source control with git +----------------------- + +MicroPython is hosted on `GitHub `_ and uses +`Git `_ for source control. The workflow is such that +code is pulled and pushed to and from the main repository. Install the respective version +of Git for your operating system to follow through the rest of the steps. + +.. note:: + For a reference on the installation instructions, please refer to + the `Git installation instructions `_. + Learn about the basic git commands in this `Git Handbook `_ + or any other sources on the internet. + +Get the code +------------ + +It is recommended that you maintain a fork of the MicroPython repository for your development purposes. +The process of obtaining the source code includes the following: + +#. Fork the repository https://github.com/micropython/micropython +#. You will now have a fork at /micropython>. +#. Clone the forked repository using the following command: + +.. code-block:: bash + + $ git clone https://github.com//micropython + +Then, `configure the remote repositories `_ to be able to +collaborate on the MicroPython project. + +Configure remote upstream: + +.. code-block:: bash + + $ cd micropython + $ git remote add upstream https://github.com/micropython/micropython + +It is common to configure ``upstream`` and ``origin`` on a forked repository +to assist with sharing code changes. You can maintain your own mapping but +it is recommended that ``origin`` maps to your fork and ``upstream`` to the main +MicroPython repository. + +After the above configuration, your setup should be similar to this: + +.. code-block:: bash + + $ git remote -v + origin https://github.com//micropython (fetch) + origin https://github.com//micropython (push) + upstream https://github.com/micropython/micropython (fetch) + upstream https://github.com/micropython/micropython (push) + +You should now have a copy of the source code. By default, you are pointing +to the master branch. To prepare for further development, it is recommended +to work on a development branch. + +.. code-block:: bash + + $ git checkout -b dev-branch + +You can give it any name. You will have to compile MicroPython whenever you change +to a different branch. + +Compile and build the code +-------------------------- + +When compiling MicroPython, you compile a specific :term:`port`, usually +targeting a specific :ref:`board `. Start by installing the required dependencies. +Then build the MicroPython cross-compiler before you can successfully compile and build. +This applies specifically when using Linux to compile. +The Windows instructions are provided in a later section. + +.. _required_dependencies: + +Required dependencies +~~~~~~~~~~~~~~~~~~~~~ + +Install the required dependencies for Linux: + +.. code-block:: bash + + $ sudo apt-get install build-essential libffi-dev git pkg-config + +For the stm32 port, the ARM cross-compiler is required: + +.. code-block:: bash + + $ sudo apt-get install arm-none-eabi-gcc arm-none-eabi-binutils arm-none-eabi-newlib + +See the `ARM GCC +toolchain `_ +for the latest details. + +Python is also required. Python 2 is supported for now, but we recommend using Python 3. +Check that you have Python available on your system: + +.. code-block:: bash + + $ python3 + Python 3.5.0 (default, Jul 17 2020, 14:04:10) + [GCC 5.4.0 20160609] on linux + Type "help", "copyright", "credits" or "license" for more information. + >>> + +All supported ports have different dependency requirements, see their respective +`readme files `_. + +Building the MicroPython cross-compiler +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Almost all ports require building ``mpy-cross`` first to perform pre-compilation +of Python code that will be included in the port firmware: + +.. code-block:: bash + + $ cd mpy-cross + $ make + +.. note:: + Note that, ``mpy-cross`` must be built for the host architecture + and not the target architecture. + +If it built successfully, you should see a message similar to this: + +.. code-block:: bash + + LINK mpy-cross + text data bss dec hex filename + 279328 776 880 280984 44998 mpy-cross + +.. note:: + + Use ``make -C mpy-cross`` to build the cross-compiler in one statement + without moving to the ``mpy-cross`` directory otherwise, you will need + to do ``cd ..`` for the next steps. + +Building the Unix port of MicroPython +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Unix port is a version of MicroPython that runs on Linux, macOS, and other Unix-like operating systems. +It's extremely useful for developing MicroPython as it avoids having to deploy your code to a device to test it. +In many ways, it works a lot like CPython's python binary. + +To build for the Unix port, make sure all Linux related dependencies are installed as detailed in the +required dependencies section. See the :ref:`required_dependencies` +to make sure that all dependencies are installed for this port. Also, make sure you have a working +environment for ``gcc`` and ``GNU make``. Ubuntu 20.04 has been used for the example +below but other unixes ought to work with little modification: + +.. code-block:: bash + + $ gcc --version + gcc (Ubuntu 9.3.0-10ubuntu2) 9.3.0 + Copyright (C) 2019 Free Software Foundation, Inc. + This is free software; see the source for copying conditions. There is NO + warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.then build: + +.. code-block:: bash + + $ cd ports/unix + $ make submodules + $ make + +If MicroPython built correctly, you should see the following: + +.. code-block:: bash + + LINK micropython + text data bss dec hex filename + 412033 5680 2496 420209 66971 micropython + +Now run it: + +.. code-block:: bash + + $ ./micropython + MicroPython v1.13-38-gc67012d-dirty on 2020-09-13; linux version + Use Ctrl-D to exit, Ctrl-E for paste mode + >>> print("hello world") + hello world + >>> + +Building the Windows port +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Windows port includes a Visual Studio project file micropython.vcxproj that you can use to build micropython.exe. +It can be opened in Visual Studio or built from the command line using msbuild. Alternatively, it can be built using mingw, +either in Windows with Cygwin, or on Linux. +See `windows port documentation `_ for more information. + +Building the STM32 port +~~~~~~~~~~~~~~~~~~~~~~~ + +Like the Unix port, you need to install some required dependencies +as detailed in the :ref:`required_dependencies` section, then build: + +.. code-block:: bash + + $ cd ports/stm32 + $ make submodules + $ make + +Please refer to the `stm32 documentation `_ +for more details on flashing the firmware. + +.. note:: + See the :ref:`required_dependencies` to make sure that all dependencies are installed for this port. + The cross-compiler is needed. ``arm-none-eabi-gcc`` should also be in the $PATH or specified manually + via CROSS_COMPILE, either by setting the environment variable or in the ``make`` command line arguments. + +You can also specify which board to use: + +.. code-block:: bash + + $ cd ports/stm32 + $ make submodules + $ make BOARD= + +See `ports/stm32/boards `_ +for the available boards. e.g. "PYBV11" or "NUCLEO_WB55". + +Building the documentation +-------------------------- + +MicroPython documentation is created using ``Sphinx``. If you have already +installed Python, then install ``Sphinx`` using ``pip``. It is recommended +that you use a virtual environment: + +.. code-block:: bash + + $ python3 -m venv env + $ source env/bin/activate + $ pip install sphinx + +Navigate to the ``docs`` directory: + +.. code-block:: bash + + $ cd docs + +Build the docs: + +.. code-block:: bash + + $ make html + +Open ``docs/build/html/index.html`` in your browser to view the docs locally. Refer to the +documentation on `importing your documentation +`_ to use Read the Docs. + +Running the tests +----------------- + +To run all tests in the test suite on the Unix port use: + +.. code-block:: bash + + $ cd ports/unix + $ make test + +To run a selection of tests on a board/device connected over USB use: + +.. code-block:: bash + + $ cd tests + $ ./run-tests --target minimal --device /dev/ttyACM0 + +See also :ref:`writingtests`. + +Folder structure +---------------- + +There are a couple of directories to take note of in terms of where certain implementation details +are. The following is a break down of the top-level folders in the source code. + +py + + Contains the compiler, runtime, and core library implementation. + +mpy-cross + + Has the MicroPython cross-compiler which pre-compiles the Python scripts to bytecode. + +ports + + Code for all the versions of MicroPython for the supported ports. + +lib + + Low-level C libraries used by any port which are mostly 3rd-party libraries. + +drivers + + Has drivers for specific hardware and intended to work across multiple ports. + +extmod + + Contains a C implementation of more non-core modules. + +docs + + Has the standard documentation found at https://docs.micropython.org/. + +tests + + An implementation of the test suite. + +tools + + Contains helper tools including the ``upip`` and the ``pyboard.py`` module. + +examples + + Example code for building MicroPython as a library as well as native modules. diff --git a/docs/develop/img/bitmap.png b/docs/develop/img/bitmap.png new file mode 100644 index 0000000000000000000000000000000000000000..87de81d769431782a9cb77c0bc7e11f6e3c07044 GIT binary patch literal 6388 zcmcIpbx>Ptn}>2+thoD4inK^+OK~XDP^?ICDNu?#0fL4UD8=rD6bZ$-Sc6mCf>T_I z6n6~??hOPscXwy!+kbXv_WSnCoH^%tW}bKEJ@5Pe=Z;qL{#c(AUz@? zVrIfEN_K~kp)m^xCOk;ImDLT%$jIhbbyf%=n~#c#kG_Y!kDs-d9g%~(hnt;v%T9wimKSp9E5wfYkoH{}5`)Lk~ym{uy+`k-^j40(xDEHRbQTHAtIZE*n zFJvevwkOwhlBnNnJ81K1@NK`kO1?s)4gu0gx74Es@^`mW~vC?`}*j%2HHFPXg;P@Ci#PuZf4cd zx>s(o==-VU)++o&ZUs$g&CUC>^8{7OTYtOWVw#6Qx5h|Fv!RJBwMW~lkY)bTaGwB< zS29 z)eEcrL#I@~HlsmPvwBW_5J{QJ94R{&a?I4U4qCOEwVHmbi%3W1L@y*$JwJlWy$ZNr zo|>AvDk147(jmvyDb1f@rSw8OnnuCFnc-VEBntRbqicI)i40t$4 zMK!Q6^V~cTV&I@!7737w?b`>yD%i-_(I-ENaE!0(oL_anNf1#>_&4nh9|3yXIC4;M zR5kcCEul`@|KZqha_YB<;m+!zS26hJI}0La$s!CVKsumWbhB6v_lkPPmAj z_!XZ7y?=O^k_xg7H-D<^0NIRaUlo0fJ_on!a5hv;%)AT{2xE}`r;nw&aqA`3k39ks zJUgc~8&W@55E1%Jn-bGciT_jnHJ$qR zxaJb@V4xFca`TFof}Ri#u7gfEG+<101n+`*t0M}xwmc4zQb{eBji74D2%!f8-LKEX zG$mVGeI6Vn`ibZW&w_EV`tqBtobVJ6%elbg@z_Lj{)GUAS74;MF-=RtH+tvqR1_(P z33W}l%5a})xN8Ph+I0Vy^E<_iHI0*}`8ep>wJyB><}pC?#l!I$t84cu!BIBZ5e3Ke zabxDBD*GizA)wG~{+o~cZ`&qpLxPE|861_Hu&Gv1A&#?S@4c@Go){gJ*nWHeZs+KE zgu+at@Vd;Kv-s`{4NKU;b^pS|ff?6q!_`{eC+xua5I%)jpatvpvo2!$)c}btCmr2l zqi#N!jjsznyi!kwmCu!J>pnoR0yFoOcp{z5zffT`Ptl5REc`8Z=r~stLAFn%D;F18p1EZ<#8B^|8*;ygI+pT(-1A2v8GEX|aNwCXTY0F>p<(^68 z(9qDIO~L*^!pQ_L9c)`%I&^&Eu(FcDOSok+WdHPKKVKou?3=6F)Eb>P1Ir8tFMhnD zN;b+(W-<31R!upmu0v*BFC9ypm*=@HNV7`{tX}P0O%G;c9qWj9Vsnswf&v0aq@?w` zPK`pU?I$lBQRoAQK{KQYexx|()x@!rsySY<*YqZ?x{BEe=Ggz*?*f;31p;LZ&IiF; zi_~ZHFJ~brOd?p**PAkqcaTeFm(t`@c=(}Qc2bh+@x1FzZ!{g30)zqFyN*-_l$b|rlJD)7MKQ72cG{#cSfO{n$ zS=%?$h*5j+^WK*cyZW%fotVc+!VG<~7}|GUI|JcXd79OF)5=kTEKikeT3m~FQHHRk zpD(*-=1QkSZUV=GvfTe3?p|Yn)R6Il*vg1Jm;G?s_Y;&ha>BW`3?MEc!5;M1!eMH5 zV%cyTc$6edBIRl4Fg{}uv0T1dVeQGu!p@Fa^s9=#{_5+{w)=Z&9xbb~j#cHi+FG|I z+Fr2SXm4-t!EAMJL-UZb2}`Zg;j?GYYRp7>!S?p{U@-XU)2BV$bV9tp-K%vR%kAmu zxxBozq}354BoL{o(%?0a4LnyC0C-YpwS%OzYLd9CM)vws^xJssuBN97^?%X-|CPeW zcSK>++}9V{b4v^_L$ldkc8OW1^nDJ(%gpP2ucTH4a`WTz{Zrc$edG8{PBwlX1$A!L)C|bWX_0m2WE4s+n5O#g1!n%o1r8ckRwSp3 zcRVf5xSl}jWRJ{=FFsRvB%(S|Rw8<9+qIuYj*ZKB0pT6n%yu%b&XRiVugAv5W>!`w zo`u8VQc_ss)ogdckj%faQ5~u(`=Yd{$VH*@7b`t0J2N|bqtcrt=>4Kc3$>TAMWLag z9;JN`VWm=r+O8%bvnwa&@`))BsjL~jsmxWwx$$CQfMZB-K$EapO<`qar8u_@4U&WG zIl$ukm+gVbfF^2J$-SP+jm2W}bU2T+{{~5;{fF^*irlo_vi{}RCC`=1BZc9E>`YYs zC9*HK9g2l5$t=>*PQOlO@7aEXpY8p^;kGu< zRv4K1wKHbCg(NAg`FPn|_(EwpOvrxQD2I1n>4a2TIV^SY^+0ud3VwjsCO z<<^n*XIacVyi8vo_84iR0_r~Vqs~|<#Wk@`GY~jM9AIH+tsUR9l(YA`wC&2Vx2iOQ zLk6f9oazmkahwj#sBW$n6vbvM&=*lEylp!6lqT%1kZxG_5F#`UZ_ZTg%W@x7d|k8A zA}z=juMLs8E$)NO5WPHLA&VnOEB43yT8HYuH$3ru%4>2?n*)mEZ&U_{IwsbDhFBYW z?tsEP;w*3DyR!^q`Diq@-=%AV9dp20<5ec~)567c0frk`sbCi1xwu}bO{SL4{th14 zD2rF>KeGjZZ@SVDSzK8d;u@Q&S%&@kWf+G}qgV(UjfN}S%pA^(i(yaAbInU-Se$@u zMVZfijggkXNrJv%2qk!x{{*Y(Z{>YT6^22F%d$?*m8wc0kd17;6#X52YYOAQyu3Ww zEBcJS|6P#1)0LK%?%`IJe5w3S*T0H{h(T_aID|ZFm#8J9GHbW}1Z6ETx97_5^LI1~?TwS_9sjIH!0QpC4LPf0N5A}{qqr?CPn{>rn{}F)H{%Pp z6CdBMElkYwYjHVjitYcB=D5IXPIGldSZe?c~{{xm(~D zb(YofSLm5_nqxQ*o423cfWv^hh5V@oK{+r9oSktLloUaQ0T!md1NlWtqdHW0d`9e5 ztILas$qcnWVA2mA(Ip9Qe^%TS8x>d)WS=A4Z_UOt^0Y1uqtCY90S|-pzmxpmf zIE0a2%QVpff_rUv^NaLqIXC#t$?=?am-A1vrSV*Xb~~$It-&7cxjt$1S6w#U3`0<}~X2d_E))7aGeY6Ljth-#5_K)`piaABH1z zIbc!CuA*<=jQN%6cS{(VK`aK5To$orz4J3suEHwL5Ka5JAdP~EsHhLCW%^EHlR^6D z_+1%GyPKXLUAd6?X&sJEws)dg%Ku2OuL{YX4uTx{1t4qmjZr0rCf5}8ZqpSgZ z&(dJM!nQP~plU&X`C2W%73DQn48KtU@g}R;X|M9Uc2m>1F_^sxQyMN(nJ;-D`8a!g zxjpEFakkFQybXpY(C^UjaFD7RVHd5=;Es%JP-Re5K&wm*`qnAoW?b8}IPjg9T$Hl9x1Im2nUteo;}d_Cl- zZCPr(ygah9<$yKAU@&<+7%dH^F; zdynLJczD_zM!DDb#VM^VChv6&_VxX6uI20{c|rHF!=j+5FMn}IFU$0zWuo?j;* z5T-LYhCVTxoIKQN6}ck=Eq>Bc6h%|{Sp(#pohf^Mg^XWwhLqIZbjSIh)+bhTJya`I6x zd5^d4y-y-&YQw~)jl?`YUt%w~FM~)J&4w_f@-uR|csR|a_k!U!}SF@KGrw7gHvG0YG@wAlYuoj+S- zJ^C`f{F&Y(4~BTH!~JJ=TY4VkplLJA_GAL2ENM0GoDtM)rZnFS5$sCmllPoJ+pxQ+Di!2_;MKF^+*R~;9J|U zs6v9!`-S#E6l)~RQ0)&e6Iv6jZeyUo3UftadJ9UB{kH%)z;YjIvOEQ$nWQ9OV1BsdQcmyfUD?&ZH!H&8h8duKeBTohGyI(% z8BIA}QI{cFBI-(h;Y(|tmQLe&nD)g|PZg9e&-^%9L~Z`5Lq=p`7cJ@Zx0$@)C?{v_4Dep%Y41aJE zHj&Aod0|p9%}G_A_*nkdF$Y%~)#3Zbick=Yzo@N^6-lJbEs&UiPhry;5)KS&U$@<~ zI?T%uXT7$v;knCpKfdnTB+Wldzw+oUuTSz5rT#c{o&UYckc0{N0qsDunGPJ_*=R&Q*SpkCVARku$vN| zTx?KmUc9R|>rp|v(tlbWes3_t-_FSt3;&}AQU4Q^-)f+1F90WtKLUXe1;+DujdC;7 zb9B9`LA~K~6JQ5`OeW8dulp)ei$zG{yS=evk-9~d(88dYMh-vh+vL}#{1tN%aYN>> z!iG>$07B#EbwXCz~ny}3A|Eo%Zy|Gk%vLZ-lv4}`kiD)l+2vden!U3psj5jvSu z^NwQ;pX{D$nT>hGthrgFMLF)L5@7MiS)hDNLX;~>r(Mhn%lECSs4y1&3F8LVAQr`U z;kbG7#q|@PD%a?>UWY#`GG22T~{7)KqhRYZ0EiZLbuUBpkUcR<)2mlImbA^a`+QT6bn5Uzg z7nz}5@q+8Nwwf}?FLQeV`67jq#rVtcIxC-yl-U($ZX@1?n3Gh7j@bvVbFH35K2-2x zXAb7p{f>rR2{{t&Tu$n=y_EOJNb2TC+1qSS=>~b*#l!0TLqI%^O&*e?;(5dLtV6Jx zYx08~?#TZ{2rl`cZQETu$E-&CeEZYEpiz6f8wW=HdaC?ppF!P-i@HOJULxyql9e8gMNCkdDm#;~Q${;~zsiJ@STg0^on~U~KRae~`hqe9NvY|F12UHrrrsWUmsd<=MEL6lKG%`h zYdw&c#lj+S5e`o9cpVLaOZnu*VmC)3XfK{FeNZ1tSfkHr>P5T~5n(o7y2nA^(0hqz zheu8Pi+Jt^X~45nAsSB)k_?V8jA;6XlLXxBN)}I+TU+Vy*H`*yJmpeZLItU%)Qp&AQIx_XcXP|8eg?!5(od^2D&cxE2m7dd zB(7TOamt8tUnjx%`~h2%%#6p*SfYr77|IlgUa{eqA(@#Tp;Ih?>}f-V{fo&NAuU9( z5#HhGIH+|li~yH2y7a#839O;;1d*_7ScyZi4e+_fIE>J|)paWIZX1c`MC^DW7Xn5G(*mzd2rOP1#Kr)|5LM8r37_ zE>c+7*RvWAcp2}Ecnk(QAL3abLKX1L~d1RP}nqFqUt*XudgVmP54<}!^r)|*S$7w%6D+^ zURD&9!Bkm3%^+s@ruU6Pm_Ah*c4p59tE?Im)02mhKpt{M@m9=>UU>Vk`~9{YRFJ|U zzWx5bm~uzCTv|}Eiju)o1^mk;+dY;w4?ukBlPlxjLvwQ(RaILHZksrzE0q`=)&+_u zogt2U0=bSo&>p9lJ%8m}gV1!rbCMsG#zi{Q%->i!-XG??1ta@n&9G9lR0sHxNZOFl z-`>?uKalUbr-XDDE-BxI3n;UNRd>!?qBF+w*B&5N7{eRieZ%3*73Gt;#VIK|3J^L; zS@Nx;eDOzK;PssG{_WFxgdY*=sZ=E|zr7X7ZH=j|MYiP6HtC2Qq=dJD=9~y9npfMC z*z;r_fa#V$gbtv4^&cY9?gpAemJ>c*{w3BxRmz}V@;Gffh`fARewBD~I3u|Rq|B!b ziZP2a|8Ju5f9TGvhg_<6$(JOH+dbUz(F%~a=pZm+L@=1p+7oBB33-_`$niANy9cAJ z8erztSqMc-QMW}(o1yjUmDI!a3kcVMz9>fx|GN3X@cmIH$-$J2lf#q=);R%GU#Xj@ zS)=S@8`DQ?einOO4e$MaR`Agh4kQ{D22xz&xJ z3~8}fMbP?T{vP)tpSMOsHDa6Iw0g0n6VQCbLPKal0RhoCp5sQf+m@*O!C7D+#gvJ8 zm1!e;LfEBES0WK_>UZAPm_JF{-OCAiRUO5*S$PuB_jjQfJo@)DO`lDH6-DL|lBw%inR(V=p{Q8YhT7iWg3(7(;YN%PY*DZX_+JDAB7jaNwvNSPE z`jqZ7$7(l1B}7`ptI5~8S66A2S>$|Jv(4yp>QCUn zcn{?au2mhogq_XOidkh2ptk84_VCe??|c*z<=zm1Xh{o{4c$4i5w0 zzn509jxsY?tI#qh>#IGrb#ZL5HFmjb_S+ITtAqJHC?U#ZifP&wjqca-bbDCEWA;dD z9JzJCqazzYT4>%uM+J|4C}js~LYt?bl*^c!fIY-ksS%rCh9pddOA!vcyxpucaq2t* zGS*m_&QTFF-Z3$~#&Cm=aBxyAP;aAixZ-n<{T3f$v&~Ymy|G$thkUs4wX4Xmnm52D zvW=(0APM7Jupc66Z~U(8JR8+z>B{dm0cj1ohdAe34?%eP&f=~lgy>s-1D zi$!$DI#PPo-D~A{)85b5?1;pZ!86o|SY-GzuL&ECy3q(jNWSCS8r4_J7gmVUGQ~jV z2NJwa!9ZY%oo;bh)Fn5!*`ptQarI^U;J>$63&Q$3RCz1@+}NQWqc$!MmDX+dc&S-7 zYs_^1d)95di8;tgpv*falTTtX^AbsTRFPZR#tymtNJ;aTg`dJTX_T>!jku}A7w;xN zGMsSc8x}VXVYhj6YcA%j1mR+%4I4x2H?;#rC@b%OBKxklr6cqAI!;VY9TrIKu9)BG zU9EiT>OBvD+nv-3$zIp}NP}i(THXntGFkZBD0}oL$o|U*>{1X@&-Hy?m?<% z-|V1Dj`*~I$@9LAkVJ`6+avRrb``Ib-@)SEXZyw-OdpPY8df%9gkpeyX`dOqZ9gc8 z#({O+KXis>Ds*xXrdq7>Zs)SO&EEkqR>n+^ttpik?^5mof&EWL4F1&LM@l=Se}>*4 z;_c0PW7PP|3Odh`eK;XuyrW{|#@~OG82)1vIxri&Hkgxh(&~|_rJ?4q`gLMoiDDjB ztPb_{WPE4W-F5hhrY}axFyEYjLx^(i^R8lp*)-xsI0H@Dro z7?zc}TS>-|J>$}aQBeqB%7^UILkvG=d*HLmt$`Nr=aHH%k7gEUq?Ogcf%axo$w=>7 z5Qf=WFNssq|I2aDuWQl1ixsUm%CEt>IR{c{?Il~aY-V1QC`KUqW2)RtJ!5lhU_@J+ z>%L90jEt-3(PujT{cqNMyLeAE)z%x(Y+o{OwiF>7FCG}jrQqjU_zlK5k zv)*=VXBXBu?S_D#=poUe`{7wp?~4}EdQ-!v_FMtLs>JbzP9k%MtHli+uU7)*6($5= zZ6+tAzG#das<56_7+pDMrLVTOOfe5K2lp!~ZF6FdUF0%RMCEVC28PYDx&f4L5=Ij_ z>iob#Ud`};m0o^$TG_jkYC5BJ;;zZ++3eM{(sD ze2PS=i-VaBg3GOIh1QX$v;+2s{^h4-hvhC zk?oLCwUKQ_ZFJeG=^aDi{j9Kvii$JW`C0OhEA7>(^Ast*PoU;MGGVjfGKKd&xKf0X zBXuUS?Cir{9vTJb-8jlrlsM=S1egG&ugs*ChVCcz8{`(a@$gW}b};+yMXd*GnIV)_^e6a~C?^r;?(Lkjv}+bgXItPWju2aw233?9VGz$)1407n zVlRTT4agjpp!POpM8w#0b#*9@w5U{z;QFs-&G>S(a7X7}Zu&B^1ZT{9LIQE_d=MY2 z5Y8ue%OUhaT|{abl@T(9tb~N|?u7lCgm>{-S@%_dK%vi%0#HqnTj&?6x;I&z!@#(3 zwOz`(a7SxcbZUC$OsM@5FEKnbKQ~9C8Oa%7`0Lxk zJma3MlN{h3QLWyL<`qJm`I|#czd+}cb1bh9HhR7Cx_l}i{GFhhcTJ&>-XnYtjI2Ez z5iSrZmz;pT733g&?r5R;gq%7bQ@98^>T$8+39(yr8~HgoMfj7n*k)euR&99_OD}ka zS9{bA$x5^$;@B%M-9n(ln?e+ zyl*M&y0*-KZfu6?V~q9asMq5XV<|r#3fJ4=47FQd!itJ~bP@Z{VlAEsTL!tBm%}Fq z(|lM$)X&+rDM(pa6MmR%dP%KlX>>X1_QPeLp`SJJ=SKy@&c^v;svl`}I&LZwXno+a zztq07nNgAM4$UR@EWEtr%0)Gl@ZDQ}#&&*65$VKU$v_@SbnElG`~BILdhik%r8m;( z>Ph!?_OFkJ*dofpby{W}El!XVFk>qU zM4ryZrQ8m1txR8jslmOxCYv(6=-qe0E6sFn634w=kExG23dsic`tNFE~BeI1EU% z1p&SZunzq{pmmqhy>L$ZWh>USOE(RK09Vu{=l>Mxw!-~CWtXYIo zPmZ1Vq!^agP2P)-@kGr@uDMQB-KN2-y@S!saI!NDAr6$PcBpRi!aN8h-Sv;_9DUyy z__irkYqR1$VEU_1HY+d^_9 zqk#aC?5lciP&f-#XP#rf6TClomt?UqQtMr;uSD~Hs;OFCpU}|EZ0ffS$!(g((=WA+ z+uM$wx0ERp(G9GZD@3mkh^F{bBAy0h%oArGD-d`~lX*5#Rha9vz}eq?TtcG@@dvhx z*n%Y|ttoS@Ha9@0f^_Qa-8{fl1f>dIueRUr(UpXE?tAN*1PA*J^bjQ>u|s>adQjq~ zXZ6HIW2UqRI1d?j=gnZ@7Z_>MH{9hd&1c|j;Aw&#cI*3uwzHG$H8)k~Ku#A@r3vE7`l)m%2eWyi89#WBvyq>t7X7AF$m10q!9!DxzW)?Fmhwef$n`4zLrn?1 z%IL~ump&ma71V+x#j${~?@Xj_WdET8!3Oj>g&4Yvf_!S(ai<_57VD0)KCa!TV{$nS zm~)tbrd=IgO_&6NA>W>tneK)unx#&yJJm-2F+JO@rlq0oJzw0^ z?N>It7pvwQ`EByj2B0Da+Simw*jpD-sQtu$xZ?5MRhaYmcFo zqZf311H&GNROQ4jL~K*%8v7V}ev8G-8T(#sP0i=Pu4;wp4Zl*4S@3TZMX)%xIOld( zqXy2rRR(hJ`VhGvM|=hWZP@$)4huAeFYZ;g8(bFK7Z^Uvo0bzr+q=l z-vA8WF!8E!y}!d{$(>IDs>P!53$S zLPdZ{i>Sw#w`ZgV?_JNd_;D*pgA#GBJ8qK5HbB9q`>ETuJwaM33W##0>))UL>?HYYBBwnv-U?+=+-0Ix_c5?7sCIr## zs`2sZ*RA!(ZaREck(3|6OQ+4Ro{R7xunw_RPd`);g%P_-||R?b(-$HoNh#)O@tsygw5S@3pVK< zzt#7re_7R!7(O+#WMyNOZ)SGtl(nV@J6Q{2CK~m=hn-Mt^q7+3vVxs4Zf?0rx(vee z^Ixd>Hq>tf1sCJdwfMCz*{Te7<^?I5UIjV7ymY_Yc&x|5EYI8Q)9yAk7k~X9jyHl+ z_}uP7oP}00v?Szm;d|Zw-VrXvpvreg)-bJRzGGFqHUs3e@)s=ijCTQqYhe?3&Sykg zj)G0Uv!wv}2 z4=eu7o)U}WRNSQtJMihcVDQx+dq!IF7Kh+VgMm2ATW)2`&j+y87NyxFA^akG82HLa zbK!60x~KXhm86GX7C6a^oNV59@APqh_GoH4aoXl^L56XsK_x6h=VoM~JHj#K9fR&h zjdX<^bIDVw8kTq(CH+Fe_6w~es4>Fw!)tjJyX_8C zJ=$JF8$zjsbei_{I+&BShvN0@XXIbi-lgoNF~R<3aTx!bj=yi4J*06Nb1xScy>o)Q zjP1Di8W6uf0hI)YeZ7;Or0xdz#?CbYJOeHr0f_&fp$ELzXwt*=lfg#W( m?$!Q}V*Iab{O2Km{}8-$!#4BCvxS3K5CAc^HY4A-ANk)54-ZcO literal 0 HcmV?d00001 diff --git a/docs/develop/index.rst b/docs/develop/index.rst index f1fd0692ec..7a6a6be67c 100644 --- a/docs/develop/index.rst +++ b/docs/develop/index.rst @@ -1,14 +1,27 @@ -Developing and building MicroPython -=================================== +MicroPython Internals +===================== -This chapter describes some options for extending MicroPython in C. Note -that it doesn't aim to be a complete guide for developing with MicroPython. -See the `getting started guide -`_ for further information. +This chapter covers a tour of MicroPython from the perspective of a developer, contributing +to MicroPython. It acts as a comprehensive resource on the implementation details of MicroPython +for both novice and expert contributors. + +Development around MicroPython usually involves modifying the core runtime, porting or +maintaining a new library. This guide describes at great depth, the implementation +details of MicroPython including a getting started guide, compiler internals, porting +MicroPython to a new platform and implementing a core MicroPython library. .. toctree:: - :maxdepth: 1 + :maxdepth: 3 - cmodules.rst + gettingstarted.rst + writingtests.rst + compiler.rst + memorymgt.rst + library.rst + optimizations.rst qstr.rst - natmod.rst + maps.rst + publiccapi.rst + extendingmicropython.rst + porting.rst + \ No newline at end of file diff --git a/docs/develop/library.rst b/docs/develop/library.rst new file mode 100644 index 0000000000..bebddcc8a3 --- /dev/null +++ b/docs/develop/library.rst @@ -0,0 +1,86 @@ +.. _internals_library: + +Implementing a Module +===================== + +This chapter details how to implement a core module in MicroPython. +MicroPython modules can be one of the following: + +- Built-in module: A general module that is be part of the MicroPython repository. +- User module: A module that is useful for your specific project that you maintain + in your own repository or private codebase. +- Dynamic module: A module that can be deployed and imported at runtime to your device. + +A module in MicroPython can be implemented in one of the following locations: + +- py/: A core library that mirrors core CPython functionality. +- extmod/: A CPython or MicroPython-specific module that is shared across multiple ports. +- ports//: A port-specific module. + +.. note:: + This chapter describes modules implemented in ``py/`` or core modules. + See :ref:`extendingmicropython` for details on implementing an external module. + For details on port-specific modules, see :ref:`porting_to_a_board`. + +Implementing a core module +-------------------------- + +Like CPython, MicroPython has core builtin modules that can be accessed through import statements. +An example is the ``gc`` module discussed in :ref:`memorymanagement`. + +.. code-block:: bash + + >>> import gc + >>> gc.enable() + >>> + +MicroPython has several other builtin standard/core modules like ``io``, ``uarray`` etc. +Adding a new core module involves several modifications. + +First, create the ``C`` file in the ``py/`` directory. In this example we are adding a +hypothetical new module ``subsystem`` in the file ``modsubsystem.c``: + +.. code-block:: c + + #include "py/builtin.h" + #include "py/runtime.h" + + #if MICROPY_PY_SUBSYSTEM + + // info() + STATIC mp_obj_t py_subsystem_info(void) { + return MP_OBJ_NEW_SMALL_INT(42); + } + MP_DEFINE_CONST_FUN_OBJ_0(subsystem_info_obj, py_subsystem_info); + + STATIC const mp_rom_map_elem_t mp_module_subsystem_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_subsystem) }, + { MP_ROM_QSTR(MP_QSTR_info), MP_ROM_PTR(&subsystem_info_obj) }, + }; + STATIC MP_DEFINE_CONST_DICT(mp_module_subsystem_globals, mp_module_subsystem_globals_table); + + const mp_obj_module_t mp_module_subsystem = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_subsystem_globals, + }; + + MP_REGISTER_MODULE(MP_QSTR_subsystem, mp_module_subsystem, MICROPY_PY_SUBSYSTEM); + + #endif + +The implementation includes a definition of all functions related to the module and adds the +functions to the module's global table in ``mp_module_subsystem_globals_table``. It also +creates the module object with ``mp_module_subsystem``. The module is then registered with +the wider system via the ``MP_REGISTER_MODULE`` macro. + +After building and running the modified MicroPython, the module should now be importable: + +.. code-block:: bash + + >>> import subsystem + >>> subsystem.info() + 42 + >>> + +Our ``info()`` function currently returns just a single number but can be extended +to do anything. Similarly, more functions can be added to this new module. diff --git a/docs/develop/maps.rst b/docs/develop/maps.rst new file mode 100644 index 0000000000..8f899fa1d3 --- /dev/null +++ b/docs/develop/maps.rst @@ -0,0 +1,63 @@ +.. _maps: + +Maps and Dictionaries +===================== + +MicroPython dictionaries and maps use techniques called open addressing and linear probing. +This chapter details both of these methods. + +Open addressing +--------------- + +`Open addressing `_ is used to resolve collisions. +Collisions are very common occurrences and happen when two items happen to hash to the same +slot or location. For example, given a hash setup as this: + +.. image:: img/collision.png + +If there is a request to fill slot ``0`` with ``70``, since the slot ``0`` is not empty, open addressing +finds the next available slot in the dictionary to service this request. This sequential search for an alternate +location is called *probing*. There are several sequence probing algorithms but MicroPython uses +linear probing that is described in the next section. + +Linear probing +-------------- + +Linear probing is one of the methods for finding an available address or slot in a dictionary. In MicroPython, +it is used with open addressing. To service the request described above, unlike other probing algorithms, +linear probing assumes a fixed interval of ``1`` between probes. The request will therefore be serviced by +placing the item in the next free slot which is slot ``4`` in our example: + +.. image:: img/linprob.png + +The same methods i.e open addressing and linear probing are used to search for an item in a dictionary. +Assume we want to search for the data item ``33``. The computed hash value will be 2. Looking at slot 2 +reveals ``33``, at this point, we return ``True``. Searching for ``70`` is quite different as there was a +collision at the time of insertion. Therefore computing the hash value is ``0`` which is currently +holding ``44``. Instead of simply returning ``False``, we perform a sequential search starting at point +``1`` until the item ``70`` is found or we encounter a free slot. This is the general way of performing +look-ups in hashes: + +.. code-block:: c + + // not yet found, keep searching in this table + pos = (pos + 1) % set->alloc; + + if (pos == start_pos) { + // search got back to starting position, so index is not in table + if (lookup_kind & MP_MAP_LOOKUP_ADD_IF_NOT_FOUND) { + if (avail_slot != NULL) { + // there was an available slot, so use that + set->used++; + *avail_slot = index; + return index; + } else { + // not enough room in table, rehash it + mp_set_rehash(set); + // restart the search for the new element + start_pos = pos = hash % set->alloc; + } + } + } else { + return MP_OBJ_NULL; + } diff --git a/docs/develop/memorymgt.rst b/docs/develop/memorymgt.rst new file mode 100644 index 0000000000..5b1690cc82 --- /dev/null +++ b/docs/develop/memorymgt.rst @@ -0,0 +1,141 @@ +.. _memorymanagement: + +Memory Management +================= + +Unlike programming languages such as C/C++, MicroPython hides memory management +details from the developer by supporting automatic memory management. +Automatic memory management is a technique used by operating systems or applications to automatically manage +the allocation and deallocation of memory. This eliminates challenges such as forgetting to +free the memory allocated to an object. Automatic memory management also avoids the critical issue of using memory +that is already released. Automatic memory management takes many forms, one of them being +garbage collection (GC). + +The garbage collector usually has two responsibilities; + +#. Allocate new objects in available memory. +#. Free unused memory. + +There are many GC algorithms but MicroPython uses the +`Mark and Sweep `_ +policy for managing memory. This algorithm has a mark phase that traverses the heap marking all +live objects while the sweep phase goes through the heap reclaiming all unmarked objects. + +Garbage collection functionality in MicroPython is available through the ``gc`` built-in +module: + +.. code-block:: bash + + >>> x = 5 + >>> x + 5 + >>> import gc + >>> gc.enable() + >>> gc.mem_alloc() + 1312 + >>> gc.mem_free() + 2071392 + >>> gc.collect() + 19 + >>> gc.disable() + >>> + +Even when ``gc.disable()`` is invoked, collection can be triggered with ``gc.collect()``. + +The object model +---------------- + +All MicroPython objects are referred to by the ``mp_obj_t`` data type. +This is usually word-sized (i.e. the same size as a pointer on the target architecture), +and can be typically 32-bit (STM32, nRF, ESP32, Unix x86) or 64-bit (Unix x64). +It can also be greater than a word-size for certain object representations, for +example ``OBJ_REPR_D`` has a 64-bit sized ``mp_obj_t`` on a 32-bit architecture. + +An ``mp_obj_t`` represents a MicroPython object, for example an integer, float, type, dict or +class instance. Some objects, like booleans and small integers, have their value stored directly +in the ``mp_obj_t`` value and do not require additional memory. Other objects have their value +store elsewhere in memory (for example on the garbage-collected heap) and their ``mp_obj_t`` contains +a pointer to that memory. A portion of ``mp_obj_t`` is the tag which tells what type of object it is. + +See ``py/mpconfig.h`` for the specific details of the available representations. + +**Pointer tagging** + +Because pointers are word-aligned, when they are stored in an ``mp_obj_t`` the +lower bits of this object handle will be zero. For example on a 32-bit architecture +the lower 2 bits will be zero: + +``********|********|********|******00`` + +These bits are reserved for purposes of storing a tag. The tag stores extra information as +opposed to introducing a new field to store that information in the object, which may be +inefficient. In MicroPython the tag tells if we are dealing with a small integer, interned +(small) string or a concrete object, and different semantics apply to each of these. + +For small integers the mapping is this: + +``********|********|********|*******1`` + +Where the asterisks hold the actual integer value. For an interned string or an immediate +object (e.g. ``True``) the layout of the ``mp_obj_t`` value is, respectively: + +``********|********|********|*****010`` + +``********|********|********|*****110`` + +While a concrete object that is none of the above takes the form: + +``********|********|********|******00`` + +The stars here correspond to the address of the concrete object in memory. + +Allocation of objects +---------------------- + +The value of a small integer is stored directly in the ``mp_obj_t`` and will be +allocated in-place, not on the heap or elsewhere. As such, creation of small +integers does not affect the heap. Similarly for interned strings that already have +their textual data stored elsewhere, and immediate values like ``None``, ``False`` +and ``True``. + +Everything else which is a concrete object is allocated on the heap and its object structure is such that +a field is reserved in the object header to store the type of the object. + +.. code-block:: bash + + +++++++++++ + + + + + type + object header + + + + +++++++++++ + + + object items + + + + + + + +++++++++++ + +The heap's smallest unit of allocation is a block, which is four machine words in +size (16 bytes on a 32-bit machine, 32 bytes on a 64-bit machine). +Another structure also allocated on the heap tracks the allocation of +objects in each block. This structure is called a *bitmap*. + +.. image:: img/bitmap.png + +The bitmap tracks whether a block is "free" or "in use" and use two bits to track this state +for each block. + +The mark-sweep garbage collector manages the objects allocated on the heap, and also +utilises the bitmap to mark objects that are still in use. +See `py/gc.c `_ +for the full implementation of these details. + +**Allocation: heap layout** + +The heap is arranged such that it consists of blocks in pools. A block +can have different properties: + +- *ATB(allocation table byte):* If set, then the block is a normal block +- *FREE:* Free block +- *HEAD:* Head of a chain of blocks +- *TAIL:* In the tail of a chain of blocks +- *MARK :* Marked head block +- *FTB(finaliser table byte):* If set, then the block has a finaliser diff --git a/docs/develop/optimizations.rst b/docs/develop/optimizations.rst new file mode 100644 index 0000000000..d972cde666 --- /dev/null +++ b/docs/develop/optimizations.rst @@ -0,0 +1,72 @@ +.. _optimizations: + +Optimizations +============= + +MicroPython uses several optimizations to save RAM but also ensure the efficient +execution of programs. This chapter discusses some of these optimizations. + +.. note:: + :ref:`qstr` and :ref:`maps` details other optimizations on strings and + dictionaries. + +Frozen bytecode +--------------- + +When MicroPython loads Python code from the filesystem, it first has to parse the file into +a temporary in-memory representation, and then generate bytecode for execution, both of which +are stored in the heap (in RAM). This can lead to significant amounts of memory being used. +The MicroPython cross compiler can be used to generate +a ``.mpy`` file, containing the pre-compiled bytecode for a Python module. This will still +be loaded into RAM, but it avoids the additional overhead of the parsing stage. + +As a further optimisation, the pre-compiled bytecode from a ``.mpy`` file can be "frozen" +into the firmware image as part of the main firmware compilation process, which means that +the bytecode will be executed from ROM. This can lead to a significant memory saving, and +reduce heap fragmentation. + +Variables +--------- + +MicroPython processes local and global variables differently. Global variables +are stored and looked up from a global dictionary that is allocated on the heap +(note that each module has its own separate dict, so separate namespace). +Local variables on the other hand are are stored on the Python value stack, which may +live on the C stack or on the heap. They are accessed directly by their offset +within the Python stack, which is more efficient than a global lookup in a dict. + +The length of global variable names also affects how much RAM is used as identifiers +are stored in RAM. The shorter the identifier, the less memory is used. + +The other aspect is that ``const`` variables that start with an underscore are treated as +proper constants and are not allocated or added in a dictionary, hence saving some memory. +These variables use ``const()`` from the MicroPython library. Therefore: + +.. code-block:: python + + from micropython import const + + X = const(1) + _Y = const(2) + foo(X, _Y) + +Compiles to: + +.. code-block:: python + + X = 1 + foo(1, 2) + +Allocation of memory +-------------------- + +Most of the common MicroPython constructs are not allocated on the heap. +However the following are: + +- Dynamic data structures like lists, mappings, etc; +- Functions, classes and object instances; +- imports; and +- First-time assignment of global variables (to create the slot in the global dict). + +For a detailed discussion on a more user-centric perspective on optimization, +see `Maximising MicroPython speed `_ diff --git a/docs/develop/porting.rst b/docs/develop/porting.rst new file mode 100644 index 0000000000..59dd570008 --- /dev/null +++ b/docs/develop/porting.rst @@ -0,0 +1,310 @@ +.. _porting_to_a_board: + +Porting MicroPython +=================== + +The MicroPython project contains several ports to different microcontroller families and +architectures. The project repository has a `ports `_ +directory containing a subdirectory for each supported port. + +A port will typically contain definitions for multiple "boards", each of which is a specific piece of +hardware that that port can run on, e.g. a development kit or device. + +The `minimal port `_ is +available as a simplified reference implementation of a MicroPython port. It can run on both the +host system and an STM32F4xx MCU. + +In general, starting a port requires: + +- Setting up the toolchain (configuring Makefiles, etc). +- Implementing boot configuration and CPU initialization. +- Initialising basic drivers required for development and debugging (e.g. GPIO, UART). +- Performing the board-specific configurations. +- Implementing the port-specific modules. + +Minimal MicroPython firmware +---------------------------- + +The best way to start porting MicroPython to a new board is by integrating a minimal +MicroPython interpreter. For this walkthrough, create a subdirectory for the new +port in the ``ports`` directory: + +.. code-block:: bash + + $ cd ports + $ mkdir example_port + +The basic MicroPython firmware is implemented in the main port file, e.g ``main.c``: + +.. code-block:: c + + #include "py/compile.h" + #include "py/gc.h" + #include "py/mperrno.h" + #include "py/stackctrl.h" + #include "lib/utils/gchelper.h" + #include "lib/utils/pyexec.h" + + // Allocate memory for the MicroPython GC heap. + static char heap[4096]; + + int main(int argc, char **argv) { + // Initialise the MicroPython runtime. + mp_stack_ctrl_init(); + gc_init(heap, heap + sizeof(heap)); + mp_init(); + mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_path), 0); + mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_argv), 0); + + // Start a normal REPL; will exit when ctrl-D is entered on a blank line. + pyexec_friendly_repl(); + + // Deinitialise the runtime. + gc_sweep_all(); + mp_deinit(); + return 0; + } + + // Handle uncaught exceptions (should never be reached in a correct C implementation). + void nlr_jump_fail(void *val) { + for (;;) { + } + } + + // Do a garbage collection cycle. + void gc_collect(void) { + gc_collect_start(); + gc_helper_collect_regs_and_stack(); + gc_collect_end(); + } + + // There is no filesystem so stat'ing returns nothing. + mp_import_stat_t mp_import_stat(const char *path) { + return MP_IMPORT_STAT_NO_EXIST; + } + + // There is no filesystem so opening a file raises an exception. + mp_lexer_t *mp_lexer_new_from_file(const char *filename) { + mp_raise_OSError(MP_ENOENT); + } + +We also need a Makefile at this point for the port: + +.. code-block:: Makefile + + # Include the core environment definitions; this will set $(TOP). + include ../../py/mkenv.mk + + # Include py core make definitions. + include $(TOP)/py/py.mk + + # Set CFLAGS and libraries. + CFLAGS = -I. -I$(BUILD) -I$(TOP) + LIBS = -lm + + # Define the required source files. + SRC_C = \ + main.c \ + mphalport.c \ + lib/mp-readline/readline.c \ + lib/utils/gchelper_generic.c \ + lib/utils/pyexec.c \ + lib/utils/stdout_helpers.c \ + + # Define the required object files. + OBJ = $(PY_CORE_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) + + # Define the top-level target, the main firmware. + all: $(BUILD)/firmware.elf + + # Define how to build the firmware. + $(BUILD)/firmware.elf: $(OBJ) + $(ECHO) "LINK $@" + $(Q)$(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + $(Q)$(SIZE) $@ + + # Include remaining core make rules. + include $(TOP)/py/mkrules.mk + +Remember to use proper tabs to indent the Makefile. + +MicroPython Configurations +-------------------------- + +After integrating the minimal code above, the next step is to create the MicroPython +configuration files for the port. The compile-time configurations are specified in +``mpconfigport.h`` and additional hardware-abstraction functions, such as time keeping, +in ``mphalport.h``. + +The following is an example of an ``mpconfigport.h`` file: + +.. code-block:: c + + #include + + // Python internal features. + #define MICROPY_ENABLE_GC (1) + #define MICROPY_HELPER_REPL (1) + #define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_TERSE) + #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) + + // Fine control over Python builtins, classes, modules, etc. + #define MICROPY_PY_ASYNC_AWAIT (0) + #define MICROPY_PY_BUILTINS_SET (0) + #define MICROPY_PY_ATTRTUPLE (0) + #define MICROPY_PY_COLLECTIONS (0) + #define MICROPY_PY_MATH (0) + #define MICROPY_PY_IO (0) + #define MICROPY_PY_STRUCT (0) + + // Type definitions for the specific machine. + + typedef intptr_t mp_int_t; // must be pointer size + typedef uintptr_t mp_uint_t; // must be pointer size + typedef long mp_off_t; + + // We need to provide a declaration/definition of alloca(). + #include + + // Define the port's name and hardware. + #define MICROPY_HW_BOARD_NAME "example-board" + #define MICROPY_HW_MCU_NAME "unknown-cpu" + + #define MP_STATE_PORT MP_STATE_VM + + #define MICROPY_PORT_ROOT_POINTERS \ + const char *readline_hist[8]; + +This configuration file contains machine-specific configurations including aspects like if different +MicroPython features should be enabled e.g. ``#define MICROPY_ENABLE_GC (1)``. Making this Setting +``(0)`` disables the feature. + +Other configurations include type definitions, root pointers, board name, microcontroller name +etc. + +Similarly, an minimal example ``mphalport.h`` file looks like this: + +.. code-block:: c + + static inline void mp_hal_set_interrupt_char(char c) {} + +Support for standard input/output +--------------------------------- + +MicroPython requires at least a way to output characters, and to have a REPL it also +requires a way to input characters. Functions for this can be implemented in the file +``mphalport.c``, for example: + +.. code-block:: c + + #include + #include "py/mpconfig.h" + + // Receive single character, blocking until one is available. + int mp_hal_stdin_rx_chr(void) { + unsigned char c = 0; + int r = read(STDIN_FILENO, &c, 1); + (void)r; + return c; + } + + // Send the string of given length. + void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { + int r = write(STDOUT_FILENO, str, len); + (void)r; + } + +These input and output functions have to be modified depending on the +specific board API. This example uses the standard input/output stream. + +Building and running +-------------------- + +At this stage the directory of the new port should contain:: + + ports/example_port/ + ├── main.c + ├── Makefile + ├── mpconfigport.h + ├── mphalport.c + └── mphalport.h + +The port can now be built by running ``make`` (or otherwise, depending on your system). + +If you are using the default compiler settings in the Makefile given above then this +will create an executable called ``build/firmware.elf`` which can be executed directly. +To get a functional REPL you may need to first configure the terminal to raw mode: + +.. code-block:: bash + + $ stty raw opost -echo + $ ./build/firmware.elf + +That should give a MicroPython REPL. You can then run commands like: + +.. code-block:: bash + + MicroPython v1.13 on 2021-01-01; example-board with unknown-cpu + >>> import usys + >>> usys.implementation + ('micropython', (1, 13, 0)) + >>> + +Use Ctrl-D to exit, and then run ``reset`` to reset the terminal. + +Adding a module to the port +--------------------------- + +To add a custom module like ``myport``, first add the module definition in a file +``modmyport.c``: + +.. code-block:: c + + #include "py/runtime.h" + + STATIC mp_obj_t myport_info(void) { + mp_printf(&mp_plat_print, "info about my port\n"); + return mp_const_none; + } + STATIC MP_DEFINE_CONST_FUN_OBJ_0(myport_info_obj, myport_info); + + STATIC const mp_rom_map_elem_t myport_module_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_myport) }, + { MP_ROM_QSTR(MP_QSTR_info), MP_ROM_PTR(&myport_info_obj) }, + }; + STATIC MP_DEFINE_CONST_DICT(myport_module_globals, myport_module_globals_table); + + const mp_obj_module_t myport_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&myport_module_globals, + }; + + MP_REGISTER_MODULE(MP_QSTR_myport, myport_module, 1); + +Note: the "1" as the third argument in ``MP_REGISTER_MODULE`` enables this new module +unconditionally. To allow it to be conditionally enabled, replace the "1" by +``MICROPY_PY_MYPORT`` and then add ``#define MICROPY_PY_MYPORT (1)`` in ``mpconfigport.h`` +accordingly. + +You will also need to edit the Makefile to add ``modmyport.c`` to the ``SRC_C`` list, and +a new line adding the same file to ``SRC_QSTR`` (so qstrs are searched for in this new file), +like this: + +.. code-block:: Makefile + + SRC_C = \ + main.c \ + modmyport.c \ + mphalport.c \ + ... + + SRC_QSTR += modport.c + +If all went correctly then, after rebuilding, you should be able to import the new module: + +.. code-block:: bash + + >>> import myport + >>> myport.info() + info about my port + >>> diff --git a/docs/develop/publiccapi.rst b/docs/develop/publiccapi.rst new file mode 100644 index 0000000000..132c7b136b --- /dev/null +++ b/docs/develop/publiccapi.rst @@ -0,0 +1,25 @@ +.. _publiccapi: + +The public C API +================ + +The public C-API comprises functions defined in all C header files in the ``py/`` +directory. Most of the important core runtime C APIs are exposed in ``runtime.h`` and +``obj.h``. + +The following is an example of public API functions from ``obj.h``: + +.. code-block:: c + + mp_obj_t mp_obj_new_list(size_t n, mp_obj_t *items); + mp_obj_t mp_obj_list_append(mp_obj_t self_in, mp_obj_t arg); + mp_obj_t mp_obj_list_remove(mp_obj_t self_in, mp_obj_t value); + void mp_obj_list_get(mp_obj_t self_in, size_t *len, mp_obj_t **items); + +At its core, any functions and macros in header files make up the public +API and can be used to access very low-level details of MicroPython. Static +inline functions in header files are fine too, such functions will be +inlined in the code when used. + +Header files in the ``ports`` directory are only exposed to the functionality +specific to a given port. diff --git a/docs/develop/qstr.rst b/docs/develop/qstr.rst index 3550a8bd42..cd1fc47862 100644 --- a/docs/develop/qstr.rst +++ b/docs/develop/qstr.rst @@ -1,3 +1,5 @@ +.. _qstr: + MicroPython string interning ============================ @@ -57,6 +59,7 @@ Processing happens in the following stages: information. Note that this step only uses files that have changed, which means that ``qstr.i.last`` will only contain data from files that have changed since the last compile. + 2. ``qstr.split`` is an empty file created after running ``makeqstrdefs.py split`` on qstr.i.last. It's just used as a dependency to indicate that the step ran. This script outputs one file per input C file, ``genhdr/qstr/...file.c.qstr``, @@ -71,8 +74,8 @@ Processing happens in the following stages: data is written to another file (``qstrdefs.collected.h.hash``) which allows it to track changes across builds. -4. ``qstrdefs.preprocessed.h`` adds in the QSTRs from qstrdefs*. It - concatenates ``qstrdefs.collected.h`` with ``qstrdefs*.h``, then it transforms +4. Generate an enumeration, each entry of which maps a ``MP_QSTR_Foo`` to it's corresponding index. + It concatenates ``qstrdefs.collected.h`` with ``qstrdefs*.h``, then it transforms each line from ``Q(Foo)`` to ``"Q(Foo)"`` so they pass through the preprocessor unchanged. Then the preprocessor is used to deal with any conditional compilation in ``qstrdefs*.h``. Then the transformation is undone back to diff --git a/docs/develop/writingtests.rst b/docs/develop/writingtests.rst new file mode 100644 index 0000000000..4bdf4dd7a6 --- /dev/null +++ b/docs/develop/writingtests.rst @@ -0,0 +1,70 @@ +.. _writingtests: + +Writing tests +============= + +Tests in MicroPython are located at the path ``tests/``. The following is a listing of +key directories and the run-tests runner script: + +.. code-block:: bash + + . + ├── basics + ├── extmod + ├── float + ├── micropython + ├── run-tests + ... + +There are subfolders maintained to categorize the tests. Add a test by creating a new file in one of the +existing folders or in a new folder. It's also possible to make custom tests outside this tests folder, +which would be recommended for a custom port. + +For example, add the following code in a file ``print.py`` in the ``tests/unix/`` subdirectory: + +.. code-block:: python + + def print_one(): + print(1) + + print_one() + +If you run your tests, this test should appear in the test output: + +.. code-block:: bash + + $ cd ports/unix + $ make tests + skip unix/extra_coverage.py + pass unix/ffi_callback.py + pass unix/ffi_float.py + pass unix/ffi_float2.py + pass unix/print.py + pass unix/time.py + pass unix/time2.py + +Tests are run by comparing the output from the test target against the output from CPython. +So any test should use print statements to indicate test results. + +For tests that can't be compared to CPython (i.e. micropython-specific functionality), +you can provide a ``.py.exp`` file which will be used as the truth for comparison. + +The other way to run tests, which is useful when running on targets other than the Unix port, is: + +.. code-block:: bash + + $ cd tests + $ ./run-tests + +Then to run on a board: + +.. code-block:: bash + + $ ./run-tests --target minimal --device /dev/ttyACM0 + +And to run only a certain set of tests (eg a directory): + +.. code-block:: bash + + $ ./run-tests -d basics + $ ./run-tests float/builtin*.py From 71ea438561b737de7d4bb6bc60412a7e409cf298 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 28 Jan 2021 13:09:44 +1100 Subject: [PATCH 285/337] extmod/vfs: Check block 0 and 1 when auto-detecting littlefs. The superblock for littlefs is in block 0 and 1, but block 0 may be erased or partially written, so block 1 must be checked if block 0 does not have a valid littlefs superblock in it. Prior to this commit, the mount of a block device which auto-detected the filysystem type would fail for littlefs if block 0 did not contain a valid superblock. That is now fixed. Signed-off-by: Damien George --- extmod/vfs.c | 37 ++++++++++---------- tests/extmod/vfs_lfs_superblock.py | 47 ++++++++++++++++++++++++++ tests/extmod/vfs_lfs_superblock.py.exp | 2 ++ 3 files changed, 69 insertions(+), 17 deletions(-) create mode 100644 tests/extmod/vfs_lfs_superblock.py create mode 100644 tests/extmod/vfs_lfs_superblock.py.exp diff --git a/extmod/vfs.c b/extmod/vfs.c index 7dca59d351..6dcdcfa22d 100644 --- a/extmod/vfs.c +++ b/extmod/vfs.c @@ -160,27 +160,30 @@ STATIC mp_obj_t mp_vfs_autodetect(mp_obj_t bdev_obj) { #if MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2 nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { - mp_obj_t vfs = MP_OBJ_NULL; + // The superblock for littlefs is in both block 0 and 1, but block 0 may be erased + // or partially written, so search both blocks 0 and 1 for the littlefs signature. mp_vfs_blockdev_t blockdev; mp_vfs_blockdev_init(&blockdev, bdev_obj); uint8_t buf[44]; - mp_vfs_blockdev_read_ext(&blockdev, 0, 8, sizeof(buf), buf); - #if MICROPY_VFS_LFS1 - if (memcmp(&buf[32], "littlefs", 8) == 0) { - // LFS1 - vfs = mp_type_vfs_lfs1.make_new(&mp_type_vfs_lfs1, 1, 0, &bdev_obj); - nlr_pop(); - return vfs; + for (size_t block_num = 0; block_num <= 1; ++block_num) { + mp_vfs_blockdev_read_ext(&blockdev, block_num, 8, sizeof(buf), buf); + #if MICROPY_VFS_LFS1 + if (memcmp(&buf[32], "littlefs", 8) == 0) { + // LFS1 + mp_obj_t vfs = mp_type_vfs_lfs1.make_new(&mp_type_vfs_lfs1, 1, 0, &bdev_obj); + nlr_pop(); + return vfs; + } + #endif + #if MICROPY_VFS_LFS2 + if (memcmp(&buf[0], "littlefs", 8) == 0) { + // LFS2 + mp_obj_t vfs = mp_type_vfs_lfs2.make_new(&mp_type_vfs_lfs2, 1, 0, &bdev_obj); + nlr_pop(); + return vfs; + } + #endif } - #endif - #if MICROPY_VFS_LFS2 - if (memcmp(&buf[0], "littlefs", 8) == 0) { - // LFS2 - vfs = mp_type_vfs_lfs2.make_new(&mp_type_vfs_lfs2, 1, 0, &bdev_obj); - nlr_pop(); - return vfs; - } - #endif nlr_pop(); } else { // Ignore exception (eg block device doesn't support extended readblocks) diff --git a/tests/extmod/vfs_lfs_superblock.py b/tests/extmod/vfs_lfs_superblock.py new file mode 100644 index 0000000000..1ac5675554 --- /dev/null +++ b/tests/extmod/vfs_lfs_superblock.py @@ -0,0 +1,47 @@ +# Test for VfsLfs using a RAM device, when the first superblock does not exist + +try: + import uos + + uos.VfsLfs2 +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + + +class RAMBlockDevice: + def __init__(self, block_size, data): + self.block_size = block_size + self.data = data + + def readblocks(self, block, buf, off): + addr = block * self.block_size + off + for i in range(len(buf)): + buf[i] = self.data[addr + i] + + def ioctl(self, op, arg): + if op == 4: # block count + return len(self.data) // self.block_size + if op == 5: # block size + return self.block_size + if op == 6: # erase block + return 0 + + +# This is a valid littlefs2 filesystem with a block size of 64 bytes. +# The first block (where the first superblock is stored) is fully erased. +lfs2_data = b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x02\x00\x00\x00\xf0\x0f\xff\xf7littlefs/\xe0\x00\x10\x00\x00\x02\x00@\x00\x00\x00\x04\x00\x00\x00\xff\x00\x00\x00\xff\xff\xff\x7f\xfe\x03\x00\x00p\x1f\xfc\x08\x1b\xb4\x14\xa7\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01\x00\x00\x00\xff\xef\xff\xf7test.txt \x00\x00\x08p\x1f\xfc\x08\x83\xf1u\xba\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + +# Create the block device from the static data (it will be read-only). +bdev = RAMBlockDevice(64, lfs2_data) + +# Create the VFS explicitly, no auto-detection is needed for this. +vfs = uos.VfsLfs2(bdev) +print(list(vfs.ilistdir())) + +# Mount the block device directly; this relies on auto-detection. +uos.mount(bdev, "/userfs") +print(uos.listdir("/userfs")) + +# Clean up. +uos.umount("/userfs") diff --git a/tests/extmod/vfs_lfs_superblock.py.exp b/tests/extmod/vfs_lfs_superblock.py.exp new file mode 100644 index 0000000000..c71bf50e82 --- /dev/null +++ b/tests/extmod/vfs_lfs_superblock.py.exp @@ -0,0 +1,2 @@ +[] +[] From d1945cc2b56bb2159224fa7027d0aadb2362430c Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 28 Jan 2021 13:13:09 +1100 Subject: [PATCH 286/337] stm32/main: Check block 0 and 1 when auto-detecting littlefs. The superblock for littlefs is in block 0 and 1, but block 0 may be erased or partially written, so block 1 must be checked if block 0 does not have a valid littlefs superblock in it. Prior to this commit, if block 0 did not contain a valid littlefs superblock (but block 1 did) then the auto-detection would fail, mounting a FAT filesystem would also fail, and the system would reformat the flash, even though it may have contained a valid littlefs filesystem. This is now fixed. Signed-off-by: Damien George --- ports/stm32/main.c | 46 +++++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/ports/stm32/main.c b/ports/stm32/main.c index fb10b96501..f0a10fa933 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -168,31 +168,35 @@ MP_NOINLINE STATIC bool init_flash_fs(uint reset_mode) { #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[64]; - ret = storage_readblocks_ext(buf, 0, 0, sizeof(buf)); + // Try to detect the block device used for the main filesystem based on the + // contents of the superblock, which can be the first or second block. mp_int_t len = -1; + uint8_t buf[64]; + for (size_t block_num = 0; block_num <= 1; ++block_num) { + ret = storage_readblocks_ext(buf, block_num, 0, sizeof(buf)); - #if MICROPY_VFS_LFS1 - 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); - uint32_t block_count = lfs1_fromle32(superblock->d.block_count); - len = block_count * block_size; - } - #endif + #if MICROPY_VFS_LFS1 + 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); + uint32_t block_count = lfs1_fromle32(superblock->d.block_count); + len = block_count * block_size; + break; + } + #endif - #if MICROPY_VFS_LFS2 - 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); - uint32_t block_count = lfs2_fromle32(superblock->block_count); - len = block_count * block_size; + #if MICROPY_VFS_LFS2 + 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); + uint32_t block_count = lfs2_fromle32(superblock->block_count); + len = block_count * block_size; + break; + } + #endif } - #endif if (len != -1) { // Detected a littlefs filesystem so create correct block device for it From c1eb2929279fdcf7f89adeb013290e7c45e24fb7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 28 Jan 2021 15:37:00 +1100 Subject: [PATCH 287/337] stm32/mboot: Don't auto-detect littlefs block size. Instead it is now passed in as an optional parameter to the ELEM_MOUNT element, with a compile-time configurable default. Signed-off-by: Damien George --- ports/stm32/mboot/fsload.c | 25 +++++++++++++++++++++---- ports/stm32/mboot/vfs.h | 4 ++-- ports/stm32/mboot/vfs_lfs.c | 26 +------------------------- 3 files changed, 24 insertions(+), 31 deletions(-) diff --git a/ports/stm32/mboot/fsload.c b/ports/stm32/mboot/fsload.c index 591b670aa0..fe98426bed 100644 --- a/ports/stm32/mboot/fsload.c +++ b/ports/stm32/mboot/fsload.c @@ -31,6 +31,11 @@ #include "pack.h" #include "vfs.h" +// Default block size used for mount operations if none given. +#ifndef MBOOT_FSLOAD_DEFAULT_BLOCK_SIZE +#define MBOOT_FSLOAD_DEFAULT_BLOCK_SIZE (4096) +#endif + #if MBOOT_FSLOAD #if !(MBOOT_VFS_FAT || MBOOT_VFS_LFS1 || MBOOT_VFS_LFS2) @@ -213,8 +218,19 @@ int fsload_process(void) { elem = ELEM_DATA_START; for (;;) { elem = elem_search(elem, ELEM_TYPE_MOUNT); - if (elem == NULL || elem[-1] != 10) { - // End of elements, or invalid MOUNT element + if (elem == NULL) { + // End of elements. + return -1; + } + uint32_t block_size; + if (elem[-1] == 10) { + // No block size given, use default. + block_size = MBOOT_FSLOAD_DEFAULT_BLOCK_SIZE; + } else if (elem[-1] == 14) { + // Block size given, extract it. + block_size = get_le32(&elem[10]); + } else { + // Invalid MOUNT element. return -1; } if (elem[0] == mount_point) { @@ -235,19 +251,20 @@ int fsload_process(void) { const stream_methods_t *methods; #if MBOOT_VFS_FAT if (elem[1] == ELEM_MOUNT_FAT) { + (void)block_size; ret = vfs_fat_mount(&ctx.fat, base_addr, byte_len); methods = &vfs_fat_stream_methods; } else #endif #if MBOOT_VFS_LFS1 if (elem[1] == ELEM_MOUNT_LFS1) { - ret = vfs_lfs1_mount(&ctx.lfs1, base_addr, byte_len); + ret = vfs_lfs1_mount(&ctx.lfs1, base_addr, byte_len, block_size); methods = &vfs_lfs1_stream_methods; } else #endif #if MBOOT_VFS_LFS2 if (elem[1] == ELEM_MOUNT_LFS2) { - ret = vfs_lfs2_mount(&ctx.lfs2, base_addr, byte_len); + ret = vfs_lfs2_mount(&ctx.lfs2, base_addr, byte_len, block_size); methods = &vfs_lfs2_stream_methods; } else #endif diff --git a/ports/stm32/mboot/vfs.h b/ports/stm32/mboot/vfs.h index 6cf883a139..22bb989367 100644 --- a/ports/stm32/mboot/vfs.h +++ b/ports/stm32/mboot/vfs.h @@ -65,7 +65,7 @@ typedef struct _vfs_lfs1_context_t { extern const stream_methods_t vfs_lfs1_stream_methods; -int vfs_lfs1_mount(vfs_lfs1_context_t *ctx, uint32_t base_addr, uint32_t byte_len); +int vfs_lfs1_mount(vfs_lfs1_context_t *ctx, uint32_t base_addr, uint32_t byte_len, uint32_t block_size); #endif @@ -89,7 +89,7 @@ typedef struct _vfs_lfs2_context_t { extern const stream_methods_t vfs_lfs2_stream_methods; -int vfs_lfs2_mount(vfs_lfs2_context_t *ctx, uint32_t base_addr, uint32_t byte_len); +int vfs_lfs2_mount(vfs_lfs2_context_t *ctx, uint32_t base_addr, uint32_t byte_len, uint32_t block_size); #endif diff --git a/ports/stm32/mboot/vfs_lfs.c b/ports/stm32/mboot/vfs_lfs.c index e4dc511db5..3e4c9caf16 100644 --- a/ports/stm32/mboot/vfs_lfs.c +++ b/ports/stm32/mboot/vfs_lfs.c @@ -43,10 +43,6 @@ #define VFS_LFSx_MOUNT vfs_lfs1_mount #define VFS_LFSx_STREAM_METHODS vfs_lfs1_stream_methods -#define SUPERBLOCK_MAGIC_OFFSET (40) -#define SUPERBLOCK_BLOCK_SIZE_OFFSET (28) -#define SUPERBLOCK_BLOCK_COUNT_OFFSET (32) - static uint8_t lfs_read_buffer[LFS_READ_SIZE]; static uint8_t lfs_prog_buffer[LFS_PROG_SIZE]; static uint8_t lfs_lookahead_buffer[LFS_LOOKAHEAD_SIZE / 8]; @@ -59,10 +55,6 @@ static uint8_t lfs_lookahead_buffer[LFS_LOOKAHEAD_SIZE / 8]; #define VFS_LFSx_MOUNT vfs_lfs2_mount #define VFS_LFSx_STREAM_METHODS vfs_lfs2_stream_methods -#define SUPERBLOCK_MAGIC_OFFSET (8) -#define SUPERBLOCK_BLOCK_SIZE_OFFSET (24) -#define SUPERBLOCK_BLOCK_COUNT_OFFSET (28) - static uint8_t lfs_read_buffer[LFS_CACHE_SIZE]; static uint8_t lfs_prog_buffer[LFS_CACHE_SIZE]; static uint8_t lfs_lookahead_buffer[LFS_LOOKAHEAD_SIZE]; @@ -90,23 +82,7 @@ static int dev_sync(const struct LFSx_API (config) * c) { return LFSx_MACRO(_ERR_OK); } -int VFS_LFSx_MOUNT(VFS_LFSx_CONTEXT_T *ctx, uint32_t base_addr, uint32_t byte_len) { - // Read start of superblock. - uint8_t buf[48]; - hw_read(base_addr, sizeof(buf), buf); - - // Verify littlefs and detect block size. - if (memcmp(&buf[SUPERBLOCK_MAGIC_OFFSET], "littlefs", 8) != 0) { - return -1; - } - uint32_t block_size = get_le32(&buf[SUPERBLOCK_BLOCK_SIZE_OFFSET]); - uint32_t block_count = get_le32(&buf[SUPERBLOCK_BLOCK_COUNT_OFFSET]); - - // Verify size of volume. - if (block_size * block_count != byte_len) { - return -1; - } - +int VFS_LFSx_MOUNT(VFS_LFSx_CONTEXT_T *ctx, uint32_t base_addr, uint32_t byte_len, uint32_t block_size) { ctx->bdev_base_addr = base_addr; struct LFSx_API (config) *config = &ctx->config; From 0efa0b54374e3ea0cbaf455c41945800479b5bc8 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 28 Jan 2021 15:46:02 +1100 Subject: [PATCH 288/337] stm32/mboot: Add ELEM_TYPE_STATUS element so application can get status. This new element takes the form: (ELEM_TYPE_STATUS, 4,
). If this element is present in the mboot command then mboot will store to the given address the result of the filesystem firmware update process. The address can for example be an RTC backup register. Signed-off-by: Damien George --- 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/mboot/main.c | 9 ++++++++- ports/stm32/mboot/mboot.h | 1 + 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/stm32f4xx_hal_conf_base.h b/ports/stm32/boards/stm32f4xx_hal_conf_base.h index 91f064835e..057a9e81e4 100644 --- a/ports/stm32/boards/stm32f4xx_hal_conf_base.h +++ b/ports/stm32/boards/stm32f4xx_hal_conf_base.h @@ -54,6 +54,7 @@ #include "stm32f4xx_hal_usart.h" #include "stm32f4xx_hal_wwdg.h" #include "stm32f4xx_ll_adc.h" +#include "stm32f4xx_ll_pwr.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 1a3fca3ac8..6e7dff3042 100644 --- a/ports/stm32/boards/stm32f7xx_hal_conf_base.h +++ b/ports/stm32/boards/stm32f7xx_hal_conf_base.h @@ -54,6 +54,7 @@ #include "stm32f7xx_hal_usart.h" #include "stm32f7xx_hal_wwdg.h" #include "stm32f7xx_ll_adc.h" +#include "stm32f7xx_ll_pwr.h" #include "stm32f7xx_ll_rtc.h" // Enable various HAL modules diff --git a/ports/stm32/boards/stm32h7xx_hal_conf_base.h b/ports/stm32/boards/stm32h7xx_hal_conf_base.h index 231f1ac7f4..a451cfde76 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_pwr.h" #include "stm32h7xx_ll_rtc.h" // Enable various HAL modules diff --git a/ports/stm32/mboot/main.c b/ports/stm32/mboot/main.c index 0846d97cf3..1395949f2e 100644 --- a/ports/stm32/mboot/main.c +++ b/ports/stm32/mboot/main.c @@ -1443,7 +1443,14 @@ enter_bootloader: // Application passed through elements, validate then process them const uint8_t *elem_end = elem_search(ELEM_DATA_START, ELEM_TYPE_END); if (elem_end != NULL && elem_end[-1] == 0) { - fsload_process(); + int ret = fsload_process(); + // If there is a valid ELEM_TYPE_STATUS element then store the status in the given location. + const uint8_t *elem_status = elem_search(ELEM_DATA_START, ELEM_TYPE_STATUS); + if (elem_status != NULL && elem_status[-1] == 4) { + uint32_t *status_ptr = (uint32_t *)get_le32(&elem_status[0]); + LL_PWR_EnableBkUpAccess(); // In case status_ptr points to backup registers + *status_ptr = ret; + } } // Always reset because the application is expecting to resume led_state_all(0); diff --git a/ports/stm32/mboot/mboot.h b/ports/stm32/mboot/mboot.h index e4ed3cecc7..853a86968e 100644 --- a/ports/stm32/mboot/mboot.h +++ b/ports/stm32/mboot/mboot.h @@ -43,6 +43,7 @@ enum { ELEM_TYPE_END = 1, ELEM_TYPE_MOUNT, ELEM_TYPE_FSLOAD, + ELEM_TYPE_STATUS, }; enum { From bd7110a3d50d2bcfaa5527fdb5d7305badba1b82 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 29 Jan 2021 00:40:49 +1100 Subject: [PATCH 289/337] stm32/mboot: Introduce MBOOT_ERRNO_xxx constants and use them. So that a failed update via fsload can be more easily diagnosed. Signed-off-by: Damien George --- ports/stm32/mboot/fsload.c | 30 +++++++++++++++--------------- ports/stm32/mboot/gzstream.c | 2 +- ports/stm32/mboot/main.c | 6 +++--- ports/stm32/mboot/mboot.h | 31 +++++++++++++++++++++++++++++++ ports/stm32/mboot/pack.c | 22 +++++++++++----------- ports/stm32/mboot/vfs_fat.c | 4 ++-- ports/stm32/mboot/vfs_lfs.c | 13 +++++++++++-- 7 files changed, 74 insertions(+), 34 deletions(-) diff --git a/ports/stm32/mboot/fsload.c b/ports/stm32/mboot/fsload.c index fe98426bed..9ecc25b0be 100644 --- a/ports/stm32/mboot/fsload.c +++ b/ports/stm32/mboot/fsload.c @@ -80,18 +80,18 @@ static int fsload_program_file(bool write_to_flash) { // Read file header, <5sBIB int res = input_stream_read(11, buf); if (res != 11) { - return -1; + return -MBOOT_ERRNO_DFU_READ_ERROR; } file_offset = 11; // Validate header, version 1 if (memcmp(buf, "DfuSe\x01", 6) != 0) { - return -1; + return -MBOOT_ERRNO_DFU_INVALID_HEADER; } // Must have only 1 target if (buf[10] != 1) { - return -2; + return -MBOOT_ERRNO_DFU_TOO_MANY_TARGETS; } // Get total size @@ -100,13 +100,13 @@ static int fsload_program_file(bool write_to_flash) { // Read target header, <6sBi255sII res = input_stream_read(274, buf); if (res != 274) { - return -1; + return -MBOOT_ERRNO_DFU_READ_ERROR; } file_offset += 274; // Validate target header, with alt being 0 if (memcmp(buf, "Target\x00", 7) != 0) { - return -1; + return -MBOOT_ERRNO_DFU_INVALID_TARGET; } // Get target size and number of elements @@ -120,7 +120,7 @@ static int fsload_program_file(bool write_to_flash) { // Read element header, sizeof(firmware_chunk_buf)) { // Address/length out of range. firmware_chunk_base_addr = 0; - return -1; + return -MBOOT_ERRNO_PACK_INVALID_ADDR; } // Copy in the new data piece into the chunk buffer. @@ -232,14 +232,14 @@ int mboot_pack_write(uint32_t addr, const uint8_t *src8, size_t len) { // Chunk header has the wrong version. dfu_context.status = DFU_STATUS_ERROR_FILE; dfu_context.error = MBOOT_ERROR_STR_INVALID_SIG_IDX; - return -1; + return -MBOOT_ERRNO_PACK_INVALID_VERSION; } if (firmware_chunk_buf.header.address != firmware_chunk_base_addr) { // Chunk address doesn't agree with dfu address, abort. dfu_context.status = DFU_STATUS_ERROR_ADDRESS; dfu_context.error = MBOOT_ERROR_STR_INVALID_SIG_IDX; - return -1; + return -MBOOT_ERRNO_PACK_INVALID_ADDR; } if (offset + len < sizeof(firmware_chunk_buf.header) + firmware_chunk_buf.header.length + sizeof(firmware_chunk_buf.signature)) { @@ -260,7 +260,7 @@ int mboot_pack_write(uint32_t addr, const uint8_t *src8, size_t len) { // Signature failed dfu_context.status = DFU_STATUS_ERROR_VERIFY; dfu_context.error = MBOOT_ERROR_STR_INVALID_SIG_IDX; - return -1; + return -MBOOT_ERRNO_PACK_SIGN_FAILED; } // Signature passed, we have valid chunk. @@ -275,7 +275,7 @@ int mboot_pack_write(uint32_t addr, const uint8_t *src8, size_t len) { return mboot_pack_handle_firmware(); } else { // Unsupported contents. - return -1; + return -MBOOT_ERRNO_PACK_INVALID_CHUNK; } } diff --git a/ports/stm32/mboot/vfs_fat.c b/ports/stm32/mboot/vfs_fat.c index 5120bdb104..cfa30fb12c 100644 --- a/ports/stm32/mboot/vfs_fat.c +++ b/ports/stm32/mboot/vfs_fat.c @@ -84,7 +84,7 @@ int vfs_fat_mount(vfs_fat_context_t *ctx, uint32_t base_addr, uint32_t byte_len) ctx->fatfs.drv = ctx; FRESULT res = f_mount(&ctx->fatfs); if (res != FR_OK) { - return -1; + return -MBOOT_ERRNO_VFS_FAT_MOUNT_FAILED; } return 0; } @@ -93,7 +93,7 @@ static int vfs_fat_stream_open(void *stream_in, const char *fname) { vfs_fat_context_t *stream = stream_in; FRESULT res = f_open(&stream->fatfs, &stream->fp, fname, FA_READ); if (res != FR_OK) { - return -1; + return -MBOOT_ERRNO_VFS_FAT_OPEN_FAILED; } return 0; } diff --git a/ports/stm32/mboot/vfs_lfs.c b/ports/stm32/mboot/vfs_lfs.c index 3e4c9caf16..e7fd8ce63c 100644 --- a/ports/stm32/mboot/vfs_lfs.c +++ b/ports/stm32/mboot/vfs_lfs.c @@ -37,6 +37,9 @@ #error Unsupported #endif +#define MBOOT_ERRNO_VFS_LFS_MOUNT_FAILED MBOOT_ERRNO_VFS_LFS1_MOUNT_FAILED +#define MBOOT_ERRNO_VFS_LFS_OPEN_FAILED MBOOT_ERRNO_VFS_LFS1_OPEN_FAILED + #define LFSx_MACRO(s) LFS1##s #define LFSx_API(x) lfs1_ ## x #define VFS_LFSx_CONTEXT_T vfs_lfs1_context_t @@ -49,6 +52,9 @@ static uint8_t lfs_lookahead_buffer[LFS_LOOKAHEAD_SIZE / 8]; #else +#define MBOOT_ERRNO_VFS_LFS_MOUNT_FAILED MBOOT_ERRNO_VFS_LFS2_MOUNT_FAILED +#define MBOOT_ERRNO_VFS_LFS_OPEN_FAILED MBOOT_ERRNO_VFS_LFS2_OPEN_FAILED + #define LFSx_MACRO(s) LFS2##s #define LFSx_API(x) lfs2_ ## x #define VFS_LFSx_CONTEXT_T vfs_lfs2_context_t @@ -116,7 +122,7 @@ int VFS_LFSx_MOUNT(VFS_LFSx_CONTEXT_T *ctx, uint32_t base_addr, uint32_t byte_le int ret = LFSx_API(mount)(&ctx->lfs, &ctx->config); if (ret < 0) { - return -1; + return -MBOOT_ERRNO_VFS_LFS_MOUNT_FAILED; } return 0; } @@ -126,7 +132,10 @@ static int vfs_lfs_stream_open(void *stream_in, const char *fname) { memset(&ctx->file, 0, sizeof(ctx->file)); memset(&ctx->filecfg, 0, sizeof(ctx->filecfg)); ctx->filecfg.buffer = &ctx->filebuf[0]; - LFSx_API(file_opencfg)(&ctx->lfs, &ctx->file, fname, LFSx_MACRO(_O_RDONLY), &ctx->filecfg); + int ret = LFSx_API(file_opencfg)(&ctx->lfs, &ctx->file, fname, LFSx_MACRO(_O_RDONLY), &ctx->filecfg); + if (ret < 0) { + return -MBOOT_ERRNO_VFS_LFS_OPEN_FAILED; + } return 0; } From 8f211df360f324c6f8e0fee3fe00bc0b4a9ac390 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 29 Jan 2021 11:16:00 +1100 Subject: [PATCH 290/337] stm32/mboot/fwupdate.py: Refactor update_mpy with support for STATUS. Changes are: - refactor to use new _create_element function - support extended version of MOUNT element with block size - support STATUS element Signed-off-by: Damien George --- ports/stm32/mboot/fwupdate.py | 36 ++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/ports/stm32/mboot/fwupdate.py b/ports/stm32/mboot/fwupdate.py index 65284a6ac0..d7c8f46db4 100644 --- a/ports/stm32/mboot/fwupdate.py +++ b/ports/stm32/mboot/fwupdate.py @@ -1,6 +1,7 @@ # Update Mboot or MicroPython from a .dfu.gz file on the board's filesystem # MIT license; Copyright (c) 2019-2020 Damien P. George +from micropython import const import struct, time import uzlib, machine, stm @@ -9,6 +10,12 @@ VFS_FAT = 1 VFS_LFS1 = 2 VFS_LFS2 = 3 +# Constants for creating mboot elements. +_ELEM_TYPE_END = const(1) +_ELEM_TYPE_MOUNT = const(2) +_ELEM_TYPE_FSLOAD = const(3) +_ELEM_TYPE_STATUS = const(4) + FLASH_KEY1 = 0x45670123 FLASH_KEY2 = 0xCDEF89AB @@ -156,7 +163,11 @@ def update_mboot(filename): print("Programming finished, can now reset or turn off.") -def update_mpy(filename, fs_base, fs_len, fs_type=VFS_FAT): +def _create_element(kind, body): + return bytes([kind, len(body)]) + body + + +def update_mpy(filename, fs_base, fs_len, fs_type=VFS_FAT, fs_blocksize=0, status_addr=None): # Check firmware is of .dfu or .dfu.gz type try: with open(filename, "rb") as f: @@ -168,13 +179,20 @@ def update_mpy(filename, fs_base, fs_len, fs_type=VFS_FAT): print("Firmware must be a .dfu(.gz) file.") return - ELEM_TYPE_END = 1 - ELEM_TYPE_MOUNT = 2 - ELEM_TYPE_FSLOAD = 3 + if fs_type in (VFS_LFS1, VFS_LFS2) and not fs_blocksize: + raise Exception("littlefs requires fs_blocksize parameter") + mount_point = 1 - mount = struct.pack(" Date: Fri, 29 Jan 2021 11:59:15 +1100 Subject: [PATCH 291/337] stm32/main: Introduce MICROPY_HW_FLASH_MOUNT_AT_BOOT config option. It's enabled by default to retain the existing behaviour. A board can disable this option if it manages mounting the filesystem itself, for example in frozen code. Signed-off-by: Damien George --- ports/stm32/main.c | 4 ++-- ports/stm32/mpconfigboard_common.h | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ports/stm32/main.c b/ports/stm32/main.c index f0a10fa933..38710e2651 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -153,7 +153,7 @@ STATIC mp_obj_t pyb_main(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_a } MP_DEFINE_CONST_FUN_OBJ_KW(pyb_main_obj, 1, pyb_main); -#if MICROPY_HW_ENABLE_STORAGE +#if MICROPY_HW_FLASH_MOUNT_AT_BOOT // avoid inlining to avoid stack usage within main() MP_NOINLINE STATIC bool init_flash_fs(uint reset_mode) { if (reset_mode == 3) { @@ -530,7 +530,7 @@ soft_reset: // Initialise the local flash filesystem. // Create it if needed, mount in on /flash, and set it as current dir. bool mounted_flash = false; - #if MICROPY_HW_ENABLE_STORAGE + #if MICROPY_HW_FLASH_MOUNT_AT_BOOT mounted_flash = init_flash_fs(reset_mode); #endif diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index 60fbc35fcb..f493bb5d4a 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -122,6 +122,11 @@ #define MICROPY_HW_HAS_LCD (0) #endif +// Whether to automatically mount (and boot from) the flash filesystem +#ifndef MICROPY_HW_FLASH_MOUNT_AT_BOOT +#define MICROPY_HW_FLASH_MOUNT_AT_BOOT (MICROPY_HW_ENABLE_STORAGE) +#endif + // The volume label used when creating the flash filesystem #ifndef MICROPY_HW_FLASH_FS_LABEL #define MICROPY_HW_FLASH_FS_LABEL "pybflash" From 5d68b5e22ca361676903c68a5c004bdff40dd88b Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 29 Jan 2021 23:30:02 +1100 Subject: [PATCH 292/337] tools/ci.sh: For ci_code_size_setup, update apt to install gcc-multilib. Signed-off-by: Damien George --- tools/ci.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/ci.sh b/tools/ci.sh index 86c17bb691..986ec6756e 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -33,6 +33,7 @@ function ci_code_formatting_run { # code size function ci_code_size_setup { + sudo apt-get update sudo apt-get install gcc-multilib gcc --version ci_gcc_arm_setup From 925bd67cfb1607264fae79a4fdf5e79ab1ae46aa Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 26 Sep 2020 01:17:11 +1000 Subject: [PATCH 293/337] py/objfun: Support fun.__globals__ attribute. This returns a reference to the globals dict associated with the function, ie the global scope that the function was defined in. This attribute is read-only but the dict itself is modifiable, per CPython behaviour. Signed-off-by: Damien George --- py/objfun.c | 4 ++++ tests/basics/fun_globals.py | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 tests/basics/fun_globals.py diff --git a/py/objfun.c b/py/objfun.c index 052f4b1ced..178f834431 100644 --- a/py/objfun.c +++ b/py/objfun.c @@ -355,6 +355,10 @@ void mp_obj_fun_bc_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { if (attr == MP_QSTR___name__) { dest[0] = MP_OBJ_NEW_QSTR(mp_obj_fun_get_name(self_in)); } + if (attr == MP_QSTR___globals__) { + mp_obj_fun_bc_t *self = MP_OBJ_TO_PTR(self_in); + dest[0] = MP_OBJ_FROM_PTR(self->globals); + } } #endif diff --git a/tests/basics/fun_globals.py b/tests/basics/fun_globals.py new file mode 100644 index 0000000000..3f32e8bdb0 --- /dev/null +++ b/tests/basics/fun_globals.py @@ -0,0 +1,21 @@ +# test the __globals__ attribute of a function + + +def foo(): + pass + + +if not hasattr(foo, "__globals__"): + print("SKIP") + raise SystemExit + +print(type(foo.__globals__)) +print(foo.__globals__ is globals()) + +foo.__globals__["bar"] = 123 +print(bar) + +try: + foo.__globals__ = None +except AttributeError: + print("AttributeError") From fe16e785fe76ca2c84b82512c427c7fa57176abe Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 16 Jan 2021 02:01:26 +1100 Subject: [PATCH 294/337] tools/mpy-tool.py: List frozen modules in MICROPY_FROZEN_LIST_ITEM. Signed-off-by: Damien George --- tools/mpy-tool.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tools/mpy-tool.py b/tools/mpy-tool.py index de7cfe5d63..ea756d3ee2 100755 --- a/tools/mpy-tool.py +++ b/tools/mpy-tool.py @@ -916,6 +916,17 @@ def freeze_mpy(base_qstrs, raw_codes): print(" &raw_code_%s," % rc.escaped_name) print("};") + # If a port defines MICROPY_FROZEN_LIST_ITEM then list all modules wrapped in that macro. + print("#ifdef MICROPY_FROZEN_LIST_ITEM") + for rc in raw_codes: + module_name = rc.source_file.str + if module_name.endswith("/__init__.py"): + short_name = module_name[: -len("/__init__.py")] + else: + short_name = module_name[: -len(".py")] + print('MICROPY_FROZEN_LIST_ITEM("%s", "%s")' % (short_name, module_name)) + print("#endif") + def merge_mpy(raw_codes, output_file): assert len(raw_codes) <= 31 # so var-uints all fit in 1 byte From 40d2010882409e71080f519312488f5bb951238b Mon Sep 17 00:00:00 2001 From: graham sanderson Date: Sat, 12 Dec 2020 13:04:10 -0600 Subject: [PATCH 295/337] py/asmthumb: Add support for ARMv6M in native emitter. Adds a new compile-time option MICROPY_EMIT_THUMB_ARMV7M which is enabled by default (to get existing behaviour) and which should be disabled (set to 0) when building native emitter support (@micropython.native) on ARMv6M targets. --- py/asmthumb.c | 177 +++++++++++++++++++++++++++++++++++++++++-- py/asmthumb.h | 45 +++++++++++ py/emitinlinethumb.c | 6 +- py/mpconfig.h | 5 ++ 4 files changed, 225 insertions(+), 8 deletions(-) diff --git a/py/asmthumb.c b/py/asmthumb.c index bb10935fda..e3558b2cf2 100644 --- a/py/asmthumb.c +++ b/py/asmthumb.c @@ -47,6 +47,7 @@ #define SIGNED_FIT12(x) (((x) & 0xfffff800) == 0) || (((x) & 0xfffff800) == 0xfffff800) #define SIGNED_FIT23(x) (((x) & 0xffc00000) == 0) || (((x) & 0xffc00000) == 0xffc00000) +#if MICROPY_EMIT_THUMB_ARMV7M // Note: these actually take an imm12 but the high-bit is not encoded here #define OP_ADD_W_RRI_HI(reg_src) (0xf200 | (reg_src)) #define OP_ADD_W_RRI_LO(reg_dest, imm11) ((imm11 << 4 & 0x7000) | reg_dest << 8 | (imm11 & 0xff)) @@ -55,6 +56,7 @@ #define OP_LDR_W_HI(reg_base) (0xf8d0 | (reg_base)) #define OP_LDR_W_LO(reg_dest, imm12) ((reg_dest) << 12 | (imm12)) +#endif static inline byte *asm_thumb_get_cur_to_write_bytes(asm_thumb_t *as, int n) { return mp_asm_base_get_cur_to_write_bytes(&as->base, n); @@ -122,7 +124,7 @@ void asm_thumb_entry(asm_thumb_t *as, int num_locals) { // If this Thumb machine code is run from ARM state then add a prelude // to switch to Thumb state for the duration of the function. - #if MICROPY_DYNAMIC_COMPILER || MICROPY_EMIT_ARM || (defined(__arm__) && !defined(__thumb2__)) + #if MICROPY_DYNAMIC_COMPILER || MICROPY_EMIT_ARM || (defined(__arm__) && !defined(__thumb2__) && !defined(__thumb__)) #if MICROPY_DYNAMIC_COMPILER if (mp_dynamic_compiler.native_arch == MP_NATIVE_ARCH_ARMV6) #endif @@ -171,11 +173,21 @@ void asm_thumb_entry(asm_thumb_t *as, int num_locals) { } asm_thumb_op16(as, OP_PUSH_RLIST_LR(reglist)); if (stack_adjust > 0) { + #if MICROPY_EMIT_THUMB_ARMV7M if (UNSIGNED_FIT7(stack_adjust)) { asm_thumb_op16(as, OP_SUB_SP(stack_adjust)); } else { asm_thumb_op32(as, OP_SUB_W_RRI_HI(ASM_THUMB_REG_SP), OP_SUB_W_RRI_LO(ASM_THUMB_REG_SP, stack_adjust * 4)); } + #else + int adj = stack_adjust; + // we don't expect the stack_adjust to be massive + while (!UNSIGNED_FIT7(adj)) { + asm_thumb_op16(as, OP_SUB_SP(127)); + adj -= 127; + } + asm_thumb_op16(as, OP_SUB_SP(adj)); + #endif } as->push_reglist = reglist; as->stack_adjust = stack_adjust; @@ -183,11 +195,21 @@ void asm_thumb_entry(asm_thumb_t *as, int num_locals) { void asm_thumb_exit(asm_thumb_t *as) { if (as->stack_adjust > 0) { + #if MICROPY_EMIT_THUMB_ARMV7M if (UNSIGNED_FIT7(as->stack_adjust)) { asm_thumb_op16(as, OP_ADD_SP(as->stack_adjust)); } else { asm_thumb_op32(as, OP_ADD_W_RRI_HI(ASM_THUMB_REG_SP), OP_ADD_W_RRI_LO(ASM_THUMB_REG_SP, as->stack_adjust * 4)); } + #else + int adj = as->stack_adjust; + // we don't expect the stack_adjust to be massive + while (!UNSIGNED_FIT7(adj)) { + asm_thumb_op16(as, OP_ADD_SP(127)); + adj -= 127; + } + asm_thumb_op16(as, OP_ADD_SP(adj)); + #endif } asm_thumb_op16(as, OP_POP_RLIST_PC(as->push_reglist)); } @@ -241,6 +263,8 @@ void asm_thumb_mov_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_src) { asm_thumb_op16(as, 0x4600 | op_lo); } +#if MICROPY_EMIT_THUMB_ARMV7M + // if loading lo half with movw, the i16 value will be zero extended into the r32 register! size_t asm_thumb_mov_reg_i16(asm_thumb_t *as, uint mov_op, uint reg_dest, int i16_src) { assert(reg_dest < ASM_THUMB_REG_R15); @@ -250,6 +274,16 @@ size_t asm_thumb_mov_reg_i16(asm_thumb_t *as, uint mov_op, uint reg_dest, int i1 return loc; } +#else + +void asm_thumb_mov_rlo_i16(asm_thumb_t *as, uint rlo_dest, int i16_src) { + asm_thumb_mov_rlo_i8(as, rlo_dest, (i16_src >> 8) & 0xff); + asm_thumb_lsl_rlo_rlo_i5(as, rlo_dest, rlo_dest, 8); + asm_thumb_add_rlo_i8(as, rlo_dest, i16_src & 0xff); +} + +#endif + #define OP_B_N(byte_offset) (0xe000 | (((byte_offset) >> 1) & 0x07ff)) bool asm_thumb_b_n_label(asm_thumb_t *as, uint label) { @@ -274,8 +308,13 @@ bool asm_thumb_bcc_nw_label(asm_thumb_t *as, int cond, uint label, bool wide) { asm_thumb_op16(as, OP_BCC_N(cond, rel)); return as->base.pass != MP_ASM_PASS_EMIT || SIGNED_FIT9(rel); } else { + #if MICROPY_EMIT_THUMB_ARMV7M asm_thumb_op32(as, OP_BCC_W_HI(cond, rel), OP_BCC_W_LO(rel)); return true; + #else + // this method should not be called for ARMV6M + return false; + #endif } } @@ -296,8 +335,30 @@ size_t asm_thumb_mov_reg_i32(asm_thumb_t *as, uint reg_dest, mp_uint_t i32) { size_t loc = mp_asm_base_get_code_pos(&as->base); + #if MICROPY_EMIT_THUMB_ARMV7M asm_thumb_mov_reg_i16(as, ASM_THUMB_OP_MOVW, reg_dest, i32); asm_thumb_mov_reg_i16(as, ASM_THUMB_OP_MOVT, reg_dest, i32 >> 16); + #else + // should only be called with lo reg for ARMV6M + assert(reg_dest < ASM_THUMB_REG_R8); + + // sanity check that generated code is aligned + assert(!as->base.code_base || !(3u & (uintptr_t)as->base.code_base)); + + // basically: + // (nop) + // ldr reg_dest, _data + // b 1f + // _data: .word i32 + // 1: + if (as->base.code_offset & 2u) { + asm_thumb_op16(as, ASM_THUMB_OP_NOP); + } + asm_thumb_ldr_rlo_pcrel_i8(as, reg_dest, 0); + asm_thumb_op16(as, OP_B_N(2)); + asm_thumb_op16(as, i32 & 0xffff); + asm_thumb_op16(as, i32 >> 16); + #endif return loc; } @@ -305,27 +366,68 @@ size_t asm_thumb_mov_reg_i32(asm_thumb_t *as, uint reg_dest, mp_uint_t i32) { void asm_thumb_mov_reg_i32_optimised(asm_thumb_t *as, uint reg_dest, int i32) { if (reg_dest < 8 && UNSIGNED_FIT8(i32)) { asm_thumb_mov_rlo_i8(as, reg_dest, i32); - } else if (UNSIGNED_FIT16(i32)) { - asm_thumb_mov_reg_i16(as, ASM_THUMB_OP_MOVW, reg_dest, i32); } else { - asm_thumb_mov_reg_i32(as, reg_dest, i32); + #if MICROPY_EMIT_THUMB_ARMV7M + if (UNSIGNED_FIT16(i32)) { + asm_thumb_mov_reg_i16(as, ASM_THUMB_OP_MOVW, reg_dest, i32); + } else { + asm_thumb_mov_reg_i32(as, reg_dest, i32); + } + #else + uint rlo_dest = reg_dest; + assert(rlo_dest < ASM_THUMB_REG_R8); // should never be called for ARMV6M + + bool negate = i32 < 0 && ((i32 + i32) & 0xffffffffu); // don't negate 0x80000000 + if (negate) { + i32 = -i32; + } + + uint clz = __builtin_clz(i32); + uint ctz = i32 ? __builtin_ctz(i32) : 0; + assert(clz + ctz <= 32); + if (clz + ctz >= 24) { + asm_thumb_mov_rlo_i8(as, rlo_dest, (i32 >> ctz) & 0xff); + asm_thumb_lsl_rlo_rlo_i5(as, rlo_dest, rlo_dest, ctz); + } else if (UNSIGNED_FIT16(i32)) { + asm_thumb_mov_rlo_i16(as, rlo_dest, i32); + } else { + if (negate) { + // no point in negating if we're storing in 32 bit anyway + negate = false; + i32 = -i32; + } + asm_thumb_mov_reg_i32(as, rlo_dest, i32); + } + if (negate) { + asm_thumb_neg_rlo_rlo(as, rlo_dest, rlo_dest); + } + #endif } } #define OP_STR_TO_SP_OFFSET(rlo_dest, word_offset) (0x9000 | ((rlo_dest) << 8) | ((word_offset) & 0x00ff)) #define OP_LDR_FROM_SP_OFFSET(rlo_dest, word_offset) (0x9800 | ((rlo_dest) << 8) | ((word_offset) & 0x00ff)) +static void asm_thumb_mov_local_check(asm_thumb_t *as, int word_offset) { + if (as->base.pass >= MP_ASM_PASS_EMIT) { + assert(word_offset >= 0); + if (!UNSIGNED_FIT8(word_offset)) { + mp_raise_NotImplementedError(MP_ERROR_TEXT("too many locals for native method")); + } + } +} + void asm_thumb_mov_local_reg(asm_thumb_t *as, int local_num, uint rlo_src) { assert(rlo_src < ASM_THUMB_REG_R8); int word_offset = local_num; - assert(as->base.pass < MP_ASM_PASS_EMIT || word_offset >= 0); + asm_thumb_mov_local_check(as, word_offset); asm_thumb_op16(as, OP_STR_TO_SP_OFFSET(rlo_src, word_offset)); } void asm_thumb_mov_reg_local(asm_thumb_t *as, uint rlo_dest, int local_num) { assert(rlo_dest < ASM_THUMB_REG_R8); int word_offset = local_num; - assert(as->base.pass < MP_ASM_PASS_EMIT || word_offset >= 0); + asm_thumb_mov_local_check(as, word_offset); asm_thumb_op16(as, OP_LDR_FROM_SP_OFFSET(rlo_dest, word_offset)); } @@ -341,21 +443,63 @@ void asm_thumb_mov_reg_local_addr(asm_thumb_t *as, uint rlo_dest, int local_num) void asm_thumb_mov_reg_pcrel(asm_thumb_t *as, uint rlo_dest, uint label) { mp_uint_t dest = get_label_dest(as, label); mp_int_t rel = dest - as->base.code_offset; - rel -= 4 + 4; // adjust for mov_reg_i16 and then PC+4 prefetch of add_reg_reg rel |= 1; // to stay in Thumb state when jumping to this address + #if MICROPY_EMIT_THUMB_ARMV7M + rel -= 4 + 4; // adjust for mov_reg_i16 and then PC+4 prefetch of add_reg_reg asm_thumb_mov_reg_i16(as, ASM_THUMB_OP_MOVW, rlo_dest, rel); // 4 bytes + #else + rel -= 8 + 4; // adjust for four instructions and then PC+4 prefetch of add_reg_reg + // 6 bytes + asm_thumb_mov_rlo_i16(as, rlo_dest, rel); + // 2 bytes - not always needed, but we want to keep the size the same + asm_thumb_sxth_rlo_rlo(as, rlo_dest, rlo_dest); + #endif asm_thumb_add_reg_reg(as, rlo_dest, ASM_THUMB_REG_R15); // 2 bytes } +#if MICROPY_EMIT_THUMB_ARMV7M static inline void asm_thumb_ldr_reg_reg_i12(asm_thumb_t *as, uint reg_dest, uint reg_base, uint word_offset) { asm_thumb_op32(as, OP_LDR_W_HI(reg_base), OP_LDR_W_LO(reg_dest, word_offset * 4)); } +#endif void asm_thumb_ldr_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint reg_base, uint word_offset) { if (reg_dest < ASM_THUMB_REG_R8 && reg_base < ASM_THUMB_REG_R8 && UNSIGNED_FIT5(word_offset)) { asm_thumb_ldr_rlo_rlo_i5(as, reg_dest, reg_base, word_offset); } else { + #if MICROPY_EMIT_THUMB_ARMV7M asm_thumb_ldr_reg_reg_i12(as, reg_dest, reg_base, word_offset); + #else + word_offset -= 31; + if (reg_dest < ASM_THUMB_REG_R8 && reg_base < ASM_THUMB_REG_R8) { + if (UNSIGNED_FIT8(word_offset) && (word_offset < 64 || reg_dest != reg_base)) { + if (word_offset < 64) { + if (reg_dest != reg_base) { + asm_thumb_mov_reg_reg(as, reg_dest, reg_base); + } + asm_thumb_add_rlo_i8(as, reg_dest, word_offset * 4); + } else { + asm_thumb_mov_rlo_i8(as, reg_dest, word_offset); + asm_thumb_lsl_rlo_rlo_i5(as, reg_dest, reg_dest, 2); + asm_thumb_add_rlo_rlo_rlo(as, reg_dest, reg_dest, reg_base); + } + } else { + if (reg_dest != reg_base) { + asm_thumb_mov_rlo_i16(as, reg_dest, word_offset * 4); + asm_thumb_add_rlo_rlo_rlo(as, reg_dest, reg_dest, reg_dest); + } else { + uint reg_other = reg_dest ^ 7; + asm_thumb_op16(as, OP_PUSH_RLIST((1 << reg_other))); + asm_thumb_mov_rlo_i16(as, reg_other, word_offset * 4); + asm_thumb_add_rlo_rlo_rlo(as, reg_dest, reg_dest, reg_other); + asm_thumb_op16(as, OP_POP_RLIST((1 << reg_other))); + } + } + } else { + assert(0); // should never be called for ARMV6M + } + asm_thumb_ldr_rlo_rlo_i5(as, reg_dest, reg_dest, 31); + #endif } } @@ -378,7 +522,20 @@ void asm_thumb_b_label(asm_thumb_t *as, uint label) { } else { // is a forwards jump, so need to assume it's large large_jump: + #if MICROPY_EMIT_THUMB_ARMV7M asm_thumb_op32(as, OP_BW_HI(rel), OP_BW_LO(rel)); + #else + if (SIGNED_FIT12(rel)) { + // this code path has to be the same number of instructions irrespective of rel + asm_thumb_op16(as, OP_B_N(rel)); + } else { + asm_thumb_op16(as, ASM_THUMB_OP_NOP); + if (dest != (mp_uint_t)-1) { + // we have an actual branch > 12 bits; this is not handled yet + mp_raise_NotImplementedError(MP_ERROR_TEXT("native method too big")); + } + } + #endif } } @@ -397,7 +554,13 @@ void asm_thumb_bcc_label(asm_thumb_t *as, int cond, uint label) { } else { // is a forwards jump, so need to assume it's large large_jump: + #if MICROPY_EMIT_THUMB_ARMV7M asm_thumb_op32(as, OP_BCC_W_HI(cond, rel), OP_BCC_W_LO(rel)); + #else + // reverse the sense of the branch to jump over a longer branch + asm_thumb_op16(as, OP_BCC_N(cond ^ 1, 0)); + asm_thumb_b_label(as, label); + #endif } } diff --git a/py/asmthumb.h b/py/asmthumb.h index 17b694a74d..3c45336186 100644 --- a/py/asmthumb.h +++ b/py/asmthumb.h @@ -157,6 +157,7 @@ static inline void asm_thumb_sub_rlo_rlo_i3(asm_thumb_t *as, uint rlo_dest, uint #define ASM_THUMB_FORMAT_3_CMP (0x2800) #define ASM_THUMB_FORMAT_3_ADD (0x3000) #define ASM_THUMB_FORMAT_3_SUB (0x3800) +#define ASM_THUMB_FORMAT_3_LDR (0x4800) #define ASM_THUMB_FORMAT_3_ENCODE(op, rlo, i8) ((op) | ((rlo) << 8) | (i8)) @@ -177,6 +178,9 @@ static inline void asm_thumb_add_rlo_i8(asm_thumb_t *as, uint rlo, int i8) { static inline void asm_thumb_sub_rlo_i8(asm_thumb_t *as, uint rlo, int i8) { asm_thumb_format_3(as, ASM_THUMB_FORMAT_3_SUB, rlo, i8); } +static inline void asm_thumb_ldr_rlo_pcrel_i8(asm_thumb_t *as, uint rlo, uint i8) { + asm_thumb_format_3(as, ASM_THUMB_FORMAT_3_LDR, rlo, i8); +} // FORMAT 4: ALU operations @@ -202,6 +206,12 @@ void asm_thumb_format_4(asm_thumb_t *as, uint op, uint rlo_dest, uint rlo_src); static inline void asm_thumb_cmp_rlo_rlo(asm_thumb_t *as, uint rlo_dest, uint rlo_src) { asm_thumb_format_4(as, ASM_THUMB_FORMAT_4_CMP, rlo_dest, rlo_src); } +static inline void asm_thumb_mvn_rlo_rlo(asm_thumb_t *as, uint rlo_dest, uint rlo_src) { + asm_thumb_format_4(as, ASM_THUMB_FORMAT_4_MVN, rlo_dest, rlo_src); +} +static inline void asm_thumb_neg_rlo_rlo(asm_thumb_t *as, uint rlo_dest, uint rlo_src) { + asm_thumb_format_4(as, ASM_THUMB_FORMAT_4_NEG, rlo_dest, rlo_src); +} // FORMAT 5: hi register operations (add, cmp, mov, bx) // For add/cmp/mov, at least one of the args must be a high register @@ -263,6 +273,32 @@ static inline void asm_thumb_ldrb_rlo_rlo_i5(asm_thumb_t *as, uint rlo_dest, uin static inline void asm_thumb_ldrh_rlo_rlo_i5(asm_thumb_t *as, uint rlo_dest, uint rlo_base, uint byte_offset) { asm_thumb_format_9_10(as, ASM_THUMB_FORMAT_10_LDRH, rlo_dest, rlo_base, byte_offset); } +static inline void asm_thumb_lsl_rlo_rlo_i5(asm_thumb_t *as, uint rlo_dest, uint rlo_src, uint shift) { + asm_thumb_format_1(as, ASM_THUMB_FORMAT_1_LSL, rlo_dest, rlo_src, shift); +} +static inline void asm_thumb_asr_rlo_rlo_i5(asm_thumb_t *as, uint rlo_dest, uint rlo_src, uint shift) { + asm_thumb_format_1(as, ASM_THUMB_FORMAT_1_ASR, rlo_dest, rlo_src, shift); +} + +// FORMAT 11: sign/zero extend + +#define ASM_THUMB_FORMAT_11_ENCODE(op, rlo_dest, rlo_src) \ + ((op) | ((rlo_src) << 3) | (rlo_dest)) + +#define ASM_THUMB_FORMAT_11_SXTH (0xb200) +#define ASM_THUMB_FORMAT_11_SXTB (0xb240) +#define ASM_THUMB_FORMAT_11_UXTH (0xb280) +#define ASM_THUMB_FORMAT_11_UXTB (0xb2c0) + +static inline void asm_thumb_format_11(asm_thumb_t *as, uint op, uint rlo_dest, uint rlo_src) { + assert(rlo_dest < ASM_THUMB_REG_R8); + assert(rlo_src < ASM_THUMB_REG_R8); + asm_thumb_op16(as, ASM_THUMB_FORMAT_11_ENCODE(op, rlo_dest, rlo_src)); +} + +static inline void asm_thumb_sxth_rlo_rlo(asm_thumb_t *as, uint rlo_dest, uint rlo_src) { + asm_thumb_format_11(as, ASM_THUMB_FORMAT_11_SXTH, rlo_dest, rlo_src); +} // TODO convert these to above format style @@ -270,7 +306,12 @@ static inline void asm_thumb_ldrh_rlo_rlo_i5(asm_thumb_t *as, uint rlo_dest, uin #define ASM_THUMB_OP_MOVT (0xf2c0) void asm_thumb_mov_reg_reg(asm_thumb_t *as, uint reg_dest, uint reg_src); + +#if MICROPY_EMIT_THUMB_ARMV7M size_t asm_thumb_mov_reg_i16(asm_thumb_t *as, uint mov_op, uint reg_dest, int i16_src); +#else +void asm_thumb_mov_rlo_i16(asm_thumb_t *as, uint rlo_dest, int i16_src); +#endif // these return true if the destination is in range, false otherwise bool asm_thumb_b_n_label(asm_thumb_t *as, uint label); @@ -344,7 +385,11 @@ void asm_thumb_bl_ind(asm_thumb_t *as, uint fun_id, uint reg_temp); // convenien #define ASM_MOV_LOCAL_REG(as, local_num, reg) asm_thumb_mov_local_reg((as), (local_num), (reg)) #define ASM_MOV_REG_IMM(as, reg_dest, imm) asm_thumb_mov_reg_i32_optimised((as), (reg_dest), (imm)) +#if MICROPY_EMIT_THUMB_ARMV7M #define ASM_MOV_REG_IMM_FIX_U16(as, reg_dest, imm) asm_thumb_mov_reg_i16((as), ASM_THUMB_OP_MOVW, (reg_dest), (imm)) +#else +#define ASM_MOV_REG_IMM_FIX_U16(as, reg_dest, imm) asm_thumb_mov_rlo_i16((as), (reg_dest), (imm)) +#endif #define ASM_MOV_REG_IMM_FIX_WORD(as, reg_dest, imm) asm_thumb_mov_reg_i32((as), (reg_dest), (imm)) #define ASM_MOV_REG_LOCAL(as, reg_dest, local_num) asm_thumb_mov_reg_local((as), (reg_dest), (local_num)) #define ASM_MOV_REG_REG(as, reg_dest, reg_src) asm_thumb_mov_reg_reg((as), (reg_dest), (reg_src)) diff --git a/py/emitinlinethumb.c b/py/emitinlinethumb.c index cffaa4bb89..073b88da71 100644 --- a/py/emitinlinethumb.c +++ b/py/emitinlinethumb.c @@ -573,7 +573,11 @@ STATIC void emit_inline_thumb_op(emit_inline_asm_t *emit, qstr op, mp_uint_t n_a goto unknown_op; } int label_num = get_arg_label(emit, op_str, pn_args[0]); - if (!asm_thumb_bcc_nw_label(&emit->as, cc, label_num, op_len == 5 && op_str[4] == 'w')) { + bool wide = op_len == 5 && op_str[4] == 'w'; + if (wide && !ARMV7M) { + goto unknown_op; + } + if (!asm_thumb_bcc_nw_label(&emit->as, cc, label_num, wide)) { goto branch_not_in_range; } } else if (ARMV7M && op_str[0] == 'i' && op_str[1] == 't') { diff --git a/py/mpconfig.h b/py/mpconfig.h index 854188b66b..1df2e8fe91 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -304,6 +304,11 @@ #define MICROPY_EMIT_THUMB (0) #endif +// Whether to emit ARMv7-M instruction support in thumb native code +#ifndef MICROPY_EMIT_THUMB_ARMV7M +#define MICROPY_EMIT_THUMB_ARMV7M (1) +#endif + // Whether to enable the thumb inline assembler #ifndef MICROPY_EMIT_INLINE_THUMB #define MICROPY_EMIT_INLINE_THUMB (0) From c9f4c5acd6278a7a8f3376867d2927efd00aefa6 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 18 Jan 2021 00:08:59 +1100 Subject: [PATCH 296/337] py/emitnative: Ensure encoding to load prelude_offset doesn't change sz. Based on change made by Graham Sanderson. Signed-off-by: Damien George --- py/emitnative.c | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/py/emitnative.c b/py/emitnative.c index 2a657b6964..8cb5de1b4a 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -211,6 +211,7 @@ struct _emit_t { int pass; bool do_viper_types; + bool prelude_offset_uses_u16_encoding; mp_uint_t local_vtype_alloc; vtype_kind_t *local_vtype; @@ -339,6 +340,18 @@ STATIC void emit_native_mov_reg_qstr_obj(emit_t *emit, int reg_dest, qstr qst) { emit_native_mov_state_reg((emit), (local_num), (reg_temp)); \ } while (false) +#define emit_native_mov_state_imm_fix_u16_via(emit, local_num, imm, reg_temp) \ + do { \ + ASM_MOV_REG_IMM_FIX_U16((emit)->as, (reg_temp), (imm)); \ + emit_native_mov_state_reg((emit), (local_num), (reg_temp)); \ + } while (false) + +#define emit_native_mov_state_imm_fix_word_via(emit, local_num, imm, reg_temp) \ + do { \ + ASM_MOV_REG_IMM_FIX_WORD((emit)->as, (reg_temp), (imm)); \ + emit_native_mov_state_reg((emit), (local_num), (reg_temp)); \ + } while (false) + STATIC void emit_native_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scope) { DEBUG_printf("start_pass(pass=%u, scope=%p)\n", pass, scope); @@ -549,16 +562,27 @@ STATIC void emit_native_start_pass(emit_t *emit, pass_kind_t pass, scope_t *scop ASM_MOV_LOCAL_REG(emit->as, LOCAL_IDX_FUN_OBJ(emit), REG_PARENT_ARG_1); // Set code_state.ip (offset from start of this function to prelude info) + int code_state_ip_local = emit->code_state_start + OFFSETOF_CODE_STATE_IP; #if N_PRELUDE_AS_BYTES_OBJ // Prelude is a bytes object in const_table; store ip = prelude->data - fun_bc->bytecode ASM_LOAD_REG_REG_OFFSET(emit->as, REG_LOCAL_3, REG_LOCAL_3, emit->scope->num_pos_args + emit->scope->num_kwonly_args + 1); ASM_LOAD_REG_REG_OFFSET(emit->as, REG_LOCAL_3, REG_LOCAL_3, offsetof(mp_obj_str_t, data) / sizeof(uintptr_t)); ASM_LOAD_REG_REG_OFFSET(emit->as, REG_PARENT_ARG_1, REG_PARENT_ARG_1, OFFSETOF_OBJ_FUN_BC_BYTECODE); ASM_SUB_REG_REG(emit->as, REG_LOCAL_3, REG_PARENT_ARG_1); - emit_native_mov_state_reg(emit, emit->code_state_start + OFFSETOF_CODE_STATE_IP, REG_LOCAL_3); + emit_native_mov_state_reg(emit, code_state_ip_local, REG_LOCAL_3); #else - // TODO this encoding may change size in the final pass, need to make it fixed - emit_native_mov_state_imm_via(emit, emit->code_state_start + OFFSETOF_CODE_STATE_IP, emit->prelude_offset, REG_PARENT_ARG_1); + if (emit->pass == MP_PASS_CODE_SIZE) { + // Commit to the encoding size based on the value of prelude_offset in this pass. + // By using 32768 as the cut-off it is highly unlikely that prelude_offset will + // grow beyond 65535 by the end of thiss pass, and so require the larger encoding. + emit->prelude_offset_uses_u16_encoding = emit->prelude_offset < 32768; + } + if (emit->prelude_offset_uses_u16_encoding) { + assert(emit->prelude_offset <= 65535); + emit_native_mov_state_imm_fix_u16_via(emit, code_state_ip_local, emit->prelude_offset, REG_PARENT_ARG_1); + } else { + emit_native_mov_state_imm_fix_word_via(emit, code_state_ip_local, emit->prelude_offset, REG_PARENT_ARG_1); + } #endif // Set code_state.n_state (only works on little endian targets due to n_state being uint16_t) From 7a97e4351b4d78f64c3d1fbecd497481e649a83f Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 18 Jan 2021 00:29:18 +1100 Subject: [PATCH 297/337] tests: Move native for test from pybnative to micropython. And make it generic so it can be run on any target. Signed-off-by: Damien George --- tests/{pybnative/for.py => micropython/native_for.py} | 2 +- tests/{pybnative/for.py.exp => micropython/native_for.py.exp} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/{pybnative/for.py => micropython/native_for.py} (85%) rename tests/{pybnative/for.py.exp => micropython/native_for.py.exp} (100%) diff --git a/tests/pybnative/for.py b/tests/micropython/native_for.py similarity index 85% rename from tests/pybnative/for.py rename to tests/micropython/native_for.py index 50177a9ba4..c640a8d08b 100644 --- a/tests/pybnative/for.py +++ b/tests/micropython/native_for.py @@ -1,4 +1,4 @@ -import pyb +# test for native for loops @micropython.native diff --git a/tests/pybnative/for.py.exp b/tests/micropython/native_for.py.exp similarity index 100% rename from tests/pybnative/for.py.exp rename to tests/micropython/native_for.py.exp From 75fea330bffe9c916def8bf1521d7d9143eceac2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 18 Jan 2021 00:59:47 +1100 Subject: [PATCH 298/337] py/emitinlinethumb: Exclude code using #if when ARMV7M disabled. So there are no references to undeclared asm_thumb_mov_reg_i16(). Signed-off-by: Damien George --- py/emitinlinethumb.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/py/emitinlinethumb.c b/py/emitinlinethumb.c index 073b88da71..1a35e25ad3 100644 --- a/py/emitinlinethumb.c +++ b/py/emitinlinethumb.c @@ -705,23 +705,24 @@ STATIC void emit_inline_thumb_op(emit_inline_asm_t *emit, qstr op, mp_uint_t n_a } else if (op == MP_QSTR_sub) { op_code = ASM_THUMB_FORMAT_3_SUB; goto op_format_3; - } else if (ARMV7M && op == MP_QSTR_movw) { + #if ARMV7M + } else if (op == MP_QSTR_movw) { op_code = ASM_THUMB_OP_MOVW; mp_uint_t reg_dest; op_movw_movt: reg_dest = get_arg_reg(emit, op_str, pn_args[0], 15); int i_src = get_arg_i(emit, op_str, pn_args[1], 0xffff); asm_thumb_mov_reg_i16(&emit->as, op_code, reg_dest, i_src); - } else if (ARMV7M && op == MP_QSTR_movt) { + } else if (op == MP_QSTR_movt) { op_code = ASM_THUMB_OP_MOVT; goto op_movw_movt; - } else if (ARMV7M && op == MP_QSTR_movwt) { + } else if (op == MP_QSTR_movwt) { // this is a convenience instruction mp_uint_t reg_dest = get_arg_reg(emit, op_str, pn_args[0], 15); uint32_t i_src = get_arg_i(emit, op_str, pn_args[1], 0xffffffff); asm_thumb_mov_reg_i16(&emit->as, ASM_THUMB_OP_MOVW, reg_dest, i_src & 0xffff); asm_thumb_mov_reg_i16(&emit->as, ASM_THUMB_OP_MOVT, reg_dest, (i_src >> 16) & 0xffff); - } else if (ARMV7M && op == MP_QSTR_ldrex) { + } else if (op == MP_QSTR_ldrex) { mp_uint_t r_dest = get_arg_reg(emit, op_str, pn_args[0], 15); mp_parse_node_t pn_base, pn_offset; if (get_arg_addr(emit, op_str, pn_args[1], &pn_base, &pn_offset)) { @@ -729,6 +730,7 @@ STATIC void emit_inline_thumb_op(emit_inline_asm_t *emit, qstr op, mp_uint_t n_a mp_uint_t i8 = get_arg_i(emit, op_str, pn_offset, 0xff) >> 2; asm_thumb_op32(&emit->as, 0xe850 | r_base, 0x0f00 | (r_dest << 12) | i8); } + #endif } else { // search table for ldr/str instructions for (mp_uint_t i = 0; i < MP_ARRAY_SIZE(format_9_10_op_table); i++) { From 33f10381d6cf47a6c6e1ce44ce3debb44ebbaa9d Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 18 Jan 2021 02:54:18 +1100 Subject: [PATCH 299/337] lib/timeutils: Provide simple impl of extra funcs when Epoch is 1970. Dates/times must be post 2000/1/1 to work correctly with these simple implementations. Signed-off-by: Damien George --- lib/timeutils/timeutils.c | 2 +- lib/timeutils/timeutils.h | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/timeutils/timeutils.c b/lib/timeutils/timeutils.c index fc8b5e7fc8..af210d9943 100644 --- a/lib/timeutils/timeutils.c +++ b/lib/timeutils/timeutils.c @@ -158,7 +158,7 @@ mp_uint_t timeutils_seconds_since_2000(mp_uint_t year, mp_uint_t month, + (year - 2000) * 31536000; } -mp_uint_t timeutils_mktime(mp_uint_t year, mp_int_t month, mp_int_t mday, +mp_uint_t timeutils_mktime_2000(mp_uint_t year, mp_int_t month, mp_int_t mday, mp_int_t hours, mp_int_t minutes, mp_int_t seconds) { // Normalize the tuple. This allows things like: diff --git a/lib/timeutils/timeutils.h b/lib/timeutils/timeutils.h index 14da831dc8..2d40f773cc 100644 --- a/lib/timeutils/timeutils.h +++ b/lib/timeutils/timeutils.h @@ -52,12 +52,21 @@ void timeutils_seconds_since_2000_to_struct_time(mp_uint_t t, mp_uint_t timeutils_seconds_since_2000(mp_uint_t year, mp_uint_t month, mp_uint_t date, mp_uint_t hour, mp_uint_t minute, mp_uint_t second); -mp_uint_t timeutils_mktime(mp_uint_t year, mp_int_t month, mp_int_t mday, +mp_uint_t timeutils_mktime_2000(mp_uint_t year, mp_int_t month, mp_int_t mday, mp_int_t hours, mp_int_t minutes, mp_int_t seconds); // Select the Epoch used by the port. #if MICROPY_EPOCH_IS_1970 +static inline void timeutils_seconds_since_epoch_to_struct_time(uint64_t t, timeutils_struct_time_t *tm) { + // TODO this will give incorrect results for dates before 2000/1/1 + return timeutils_seconds_since_2000_to_struct_time(t - TIMEUTILS_SECONDS_1970_TO_2000, tm); +} + +static inline uint64_t timeutils_mktime(mp_uint_t year, mp_int_t month, mp_int_t mday, mp_int_t hours, mp_int_t minutes, mp_int_t seconds) { + return timeutils_mktime_2000(year, month, mday, hours, minutes, seconds) + TIMEUTILS_SECONDS_1970_TO_2000; +} + static inline uint64_t timeutils_seconds_since_epoch(mp_uint_t year, mp_uint_t month, mp_uint_t date, mp_uint_t hour, mp_uint_t minute, mp_uint_t second) { return timeutils_seconds_since_2000(year, month, date, hour, minute, second) + TIMEUTILS_SECONDS_1970_TO_2000; @@ -75,6 +84,7 @@ static inline uint64_t timeutils_nanoseconds_since_epoch_to_nanoseconds_since_19 #define timeutils_seconds_since_epoch_to_struct_time timeutils_seconds_since_2000_to_struct_time #define timeutils_seconds_since_epoch timeutils_seconds_since_2000 +#define timeutils_mktime timeutils_mktime_2000 static inline uint64_t timeutils_seconds_since_epoch_to_nanoseconds_since_1970(mp_uint_t s) { return ((uint64_t)s + TIMEUTILS_SECONDS_1970_TO_2000) * 1000000000ULL; From 15ac5a3df9b90eade2b4743e08e23ce219cb4f7f Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 18 Jan 2021 11:38:27 +1100 Subject: [PATCH 300/337] extmod/modframebuf: Change int to unsigned int in format methods args. These args are already bounds checked and clipped, and using unsigned ints can be more efficient. It also eliminates possible issues and compiler warnings with shifting of signed integers. Signed-off-by: Damien George --- extmod/modframebuf.c | 70 ++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/extmod/modframebuf.c b/extmod/modframebuf.c index 2753acc271..18b170078d 100644 --- a/extmod/modframebuf.c +++ b/extmod/modframebuf.c @@ -45,9 +45,9 @@ typedef struct _mp_obj_framebuf_t { STATIC const mp_obj_type_t mp_type_framebuf; #endif -typedef void (*setpixel_t)(const mp_obj_framebuf_t *, int, int, uint32_t); -typedef uint32_t (*getpixel_t)(const mp_obj_framebuf_t *, int, int); -typedef void (*fill_rect_t)(const mp_obj_framebuf_t *, int, int, int, int, uint32_t); +typedef void (*setpixel_t)(const mp_obj_framebuf_t *, unsigned int, unsigned int, uint32_t); +typedef uint32_t (*getpixel_t)(const mp_obj_framebuf_t *, unsigned int, unsigned int); +typedef void (*fill_rect_t)(const mp_obj_framebuf_t *, unsigned int, unsigned int, unsigned int, unsigned int, uint32_t); typedef struct _mp_framebuf_p_t { setpixel_t setpixel; @@ -66,25 +66,25 @@ typedef struct _mp_framebuf_p_t { // Functions for MHLSB and MHMSB -STATIC void mono_horiz_setpixel(const mp_obj_framebuf_t *fb, int x, int y, uint32_t col) { +STATIC void mono_horiz_setpixel(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y, uint32_t col) { size_t index = (x + y * fb->stride) >> 3; - int offset = fb->format == FRAMEBUF_MHMSB ? x & 0x07 : 7 - (x & 0x07); + unsigned int offset = fb->format == FRAMEBUF_MHMSB ? x & 0x07 : 7 - (x & 0x07); ((uint8_t *)fb->buf)[index] = (((uint8_t *)fb->buf)[index] & ~(0x01 << offset)) | ((col != 0) << offset); } -STATIC uint32_t mono_horiz_getpixel(const mp_obj_framebuf_t *fb, int x, int y) { +STATIC uint32_t mono_horiz_getpixel(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y) { size_t index = (x + y * fb->stride) >> 3; - int offset = fb->format == FRAMEBUF_MHMSB ? x & 0x07 : 7 - (x & 0x07); + unsigned int offset = fb->format == FRAMEBUF_MHMSB ? x & 0x07 : 7 - (x & 0x07); return (((uint8_t *)fb->buf)[index] >> (offset)) & 0x01; } -STATIC void mono_horiz_fill_rect(const mp_obj_framebuf_t *fb, int x, int y, int w, int h, uint32_t col) { - int reverse = fb->format == FRAMEBUF_MHMSB; - int advance = fb->stride >> 3; +STATIC void mono_horiz_fill_rect(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, uint32_t col) { + unsigned int reverse = fb->format == FRAMEBUF_MHMSB; + unsigned int advance = fb->stride >> 3; while (w--) { uint8_t *b = &((uint8_t *)fb->buf)[(x >> 3) + y * advance]; - int offset = reverse ? x & 7 : 7 - (x & 7); - for (int hh = h; hh; --hh) { + unsigned int offset = reverse ? x & 7 : 7 - (x & 7); + for (unsigned int hh = h; hh; --hh) { *b = (*b & ~(0x01 << offset)) | ((col != 0) << offset); b += advance; } @@ -94,21 +94,21 @@ STATIC void mono_horiz_fill_rect(const mp_obj_framebuf_t *fb, int x, int y, int // Functions for MVLSB format -STATIC void mvlsb_setpixel(const mp_obj_framebuf_t *fb, int x, int y, uint32_t col) { +STATIC void mvlsb_setpixel(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y, uint32_t col) { size_t index = (y >> 3) * fb->stride + x; uint8_t offset = y & 0x07; ((uint8_t *)fb->buf)[index] = (((uint8_t *)fb->buf)[index] & ~(0x01 << offset)) | ((col != 0) << offset); } -STATIC uint32_t mvlsb_getpixel(const mp_obj_framebuf_t *fb, int x, int y) { +STATIC uint32_t mvlsb_getpixel(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y) { return (((uint8_t *)fb->buf)[(y >> 3) * fb->stride + x] >> (y & 0x07)) & 0x01; } -STATIC void mvlsb_fill_rect(const mp_obj_framebuf_t *fb, int x, int y, int w, int h, uint32_t col) { +STATIC void mvlsb_fill_rect(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, uint32_t col) { while (h--) { uint8_t *b = &((uint8_t *)fb->buf)[(y >> 3) * fb->stride + x]; uint8_t offset = y & 0x07; - for (int ww = w; ww; --ww) { + for (unsigned int ww = w; ww; --ww) { *b = (*b & ~(0x01 << offset)) | ((col != 0) << offset); ++b; } @@ -118,18 +118,18 @@ STATIC void mvlsb_fill_rect(const mp_obj_framebuf_t *fb, int x, int y, int w, in // Functions for RGB565 format -STATIC void rgb565_setpixel(const mp_obj_framebuf_t *fb, int x, int y, uint32_t col) { +STATIC void rgb565_setpixel(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y, uint32_t col) { ((uint16_t *)fb->buf)[x + y * fb->stride] = col; } -STATIC uint32_t rgb565_getpixel(const mp_obj_framebuf_t *fb, int x, int y) { +STATIC uint32_t rgb565_getpixel(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y) { return ((uint16_t *)fb->buf)[x + y * fb->stride]; } -STATIC void rgb565_fill_rect(const mp_obj_framebuf_t *fb, int x, int y, int w, int h, uint32_t col) { +STATIC void rgb565_fill_rect(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, uint32_t col) { uint16_t *b = &((uint16_t *)fb->buf)[x + y * fb->stride]; while (h--) { - for (int ww = w; ww; --ww) { + for (unsigned int ww = w; ww; --ww) { *b++ = col; } b += fb->stride - w; @@ -138,7 +138,7 @@ STATIC void rgb565_fill_rect(const mp_obj_framebuf_t *fb, int x, int y, int w, i // Functions for GS2_HMSB format -STATIC void gs2_hmsb_setpixel(const mp_obj_framebuf_t *fb, int x, int y, uint32_t col) { +STATIC void gs2_hmsb_setpixel(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y, uint32_t col) { uint8_t *pixel = &((uint8_t *)fb->buf)[(x + y * fb->stride) >> 2]; uint8_t shift = (x & 0x3) << 1; uint8_t mask = 0x3 << shift; @@ -146,15 +146,15 @@ STATIC void gs2_hmsb_setpixel(const mp_obj_framebuf_t *fb, int x, int y, uint32_ *pixel = color | (*pixel & (~mask)); } -STATIC uint32_t gs2_hmsb_getpixel(const mp_obj_framebuf_t *fb, int x, int y) { +STATIC uint32_t gs2_hmsb_getpixel(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y) { uint8_t pixel = ((uint8_t *)fb->buf)[(x + y * fb->stride) >> 2]; uint8_t shift = (x & 0x3) << 1; return (pixel >> shift) & 0x3; } -STATIC void gs2_hmsb_fill_rect(const mp_obj_framebuf_t *fb, int x, int y, int w, int h, uint32_t col) { - for (int xx = x; xx < x + w; xx++) { - for (int yy = y; yy < y + h; yy++) { +STATIC void gs2_hmsb_fill_rect(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, uint32_t col) { + for (unsigned int xx = x; xx < x + w; xx++) { + for (unsigned int yy = y; yy < y + h; yy++) { gs2_hmsb_setpixel(fb, xx, yy, col); } } @@ -162,7 +162,7 @@ STATIC void gs2_hmsb_fill_rect(const mp_obj_framebuf_t *fb, int x, int y, int w, // Functions for GS4_HMSB format -STATIC void gs4_hmsb_setpixel(const mp_obj_framebuf_t *fb, int x, int y, uint32_t col) { +STATIC void gs4_hmsb_setpixel(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y, uint32_t col) { uint8_t *pixel = &((uint8_t *)fb->buf)[(x + y * fb->stride) >> 1]; if (x % 2) { @@ -172,7 +172,7 @@ STATIC void gs4_hmsb_setpixel(const mp_obj_framebuf_t *fb, int x, int y, uint32_ } } -STATIC uint32_t gs4_hmsb_getpixel(const mp_obj_framebuf_t *fb, int x, int y) { +STATIC uint32_t gs4_hmsb_getpixel(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y) { if (x % 2) { return ((uint8_t *)fb->buf)[(x + y * fb->stride) >> 1] & 0x0f; } @@ -180,16 +180,16 @@ STATIC uint32_t gs4_hmsb_getpixel(const mp_obj_framebuf_t *fb, int x, int y) { return ((uint8_t *)fb->buf)[(x + y * fb->stride) >> 1] >> 4; } -STATIC void gs4_hmsb_fill_rect(const mp_obj_framebuf_t *fb, int x, int y, int w, int h, uint32_t col) { +STATIC void gs4_hmsb_fill_rect(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, uint32_t col) { col &= 0x0f; uint8_t *pixel_pair = &((uint8_t *)fb->buf)[(x + y * fb->stride) >> 1]; uint8_t col_shifted_left = col << 4; uint8_t col_pixel_pair = col_shifted_left | col; - int pixel_count_till_next_line = (fb->stride - w) >> 1; + unsigned int pixel_count_till_next_line = (fb->stride - w) >> 1; bool odd_x = (x % 2 == 1); while (h--) { - int ww = w; + unsigned int ww = w; if (odd_x && ww > 0) { *pixel_pair = (*pixel_pair & 0xf0) | col; @@ -213,16 +213,16 @@ STATIC void gs4_hmsb_fill_rect(const mp_obj_framebuf_t *fb, int x, int y, int w, // Functions for GS8 format -STATIC void gs8_setpixel(const mp_obj_framebuf_t *fb, int x, int y, uint32_t col) { +STATIC void gs8_setpixel(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y, uint32_t col) { uint8_t *pixel = &((uint8_t *)fb->buf)[(x + y * fb->stride)]; *pixel = col & 0xff; } -STATIC uint32_t gs8_getpixel(const mp_obj_framebuf_t *fb, int x, int y) { +STATIC uint32_t gs8_getpixel(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y) { return ((uint8_t *)fb->buf)[(x + y * fb->stride)]; } -STATIC void gs8_fill_rect(const mp_obj_framebuf_t *fb, int x, int y, int w, int h, uint32_t col) { +STATIC void gs8_fill_rect(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, uint32_t col) { uint8_t *pixel = &((uint8_t *)fb->buf)[(x + y * fb->stride)]; while (h--) { memset(pixel, col, w); @@ -240,11 +240,11 @@ STATIC mp_framebuf_p_t formats[] = { [FRAMEBUF_MHMSB] = {mono_horiz_setpixel, mono_horiz_getpixel, mono_horiz_fill_rect}, }; -static inline void setpixel(const mp_obj_framebuf_t *fb, int x, int y, uint32_t col) { +static inline void setpixel(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y, uint32_t col) { formats[fb->format].setpixel(fb, x, y, col); } -static inline uint32_t getpixel(const mp_obj_framebuf_t *fb, int x, int y) { +static inline uint32_t getpixel(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y) { return formats[fb->format].getpixel(fb, x, y); } From 794df0f1d502bf715449ed6d36f16cf52b095d0b Mon Sep 17 00:00:00 2001 From: graham sanderson Date: Sun, 17 Jan 2021 21:56:34 -0600 Subject: [PATCH 301/337] py/emitnative: Support binary ops on ARMv6M without use of ite instr. --- py/asmthumb.c | 12 ++++++++++++ py/asmthumb.h | 2 ++ py/emitnative.c | 23 +++++++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/py/asmthumb.c b/py/asmthumb.c index e3558b2cf2..f7ac87fa0f 100644 --- a/py/asmthumb.c +++ b/py/asmthumb.c @@ -564,6 +564,18 @@ void asm_thumb_bcc_label(asm_thumb_t *as, int cond, uint label) { } } +void asm_thumb_bcc_rel9(asm_thumb_t *as, int cond, int rel) { + rel -= 4; // account for instruction prefetch, PC is 4 bytes ahead of this instruction + assert(SIGNED_FIT9(rel)); + asm_thumb_op16(as, OP_BCC_N(cond, rel)); +} + +void asm_thumb_b_rel12(asm_thumb_t *as, int rel) { + rel -= 4; // account for instruction prefetch, PC is 4 bytes ahead of this instruction + assert(SIGNED_FIT12(rel)); + asm_thumb_op16(as, OP_B_N(rel)); +} + #define OP_BLX(reg) (0x4780 | ((reg) << 3)) #define OP_SVC(arg) (0xdf00 | (arg)) diff --git a/py/asmthumb.h b/py/asmthumb.h index 3c45336186..17a0cca98c 100644 --- a/py/asmthumb.h +++ b/py/asmthumb.h @@ -330,6 +330,8 @@ void asm_thumb_ldr_reg_reg_i12_optimised(asm_thumb_t *as, uint reg_dest, uint re void asm_thumb_b_label(asm_thumb_t *as, uint label); // convenience: picks narrow or wide branch void asm_thumb_bcc_label(asm_thumb_t *as, int cc, uint label); // convenience: picks narrow or wide branch void asm_thumb_bl_ind(asm_thumb_t *as, uint fun_id, uint reg_temp); // convenience +void asm_thumb_bcc_rel9(asm_thumb_t *as, int cc, int rel); +void asm_thumb_b_rel12(asm_thumb_t *as, int rel); // Holds a pointer to mp_fun_table #define ASM_THUMB_REG_FUN_TABLE ASM_THUMB_REG_R7 diff --git a/py/emitnative.c b/py/emitnative.c index 8cb5de1b4a..052a505911 100644 --- a/py/emitnative.c +++ b/py/emitnative.c @@ -2454,6 +2454,7 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { asm_x86_setcc_r8(emit->as, ops[op_idx], REG_RET); #elif N_THUMB asm_thumb_cmp_rlo_rlo(emit->as, REG_ARG_2, reg_rhs); + #if MICROPY_EMIT_THUMB_ARMV7M static uint16_t ops[6 + 6] = { // unsigned ASM_THUMB_OP_ITE_CC, @@ -2473,6 +2474,28 @@ STATIC void emit_native_binary_op(emit_t *emit, mp_binary_op_t op) { asm_thumb_op16(emit->as, ops[op_idx]); asm_thumb_mov_rlo_i8(emit->as, REG_RET, 1); asm_thumb_mov_rlo_i8(emit->as, REG_RET, 0); + #else + static uint16_t ops[6 + 6] = { + // unsigned + ASM_THUMB_CC_CC, + ASM_THUMB_CC_HI, + ASM_THUMB_CC_EQ, + ASM_THUMB_CC_LS, + ASM_THUMB_CC_CS, + ASM_THUMB_CC_NE, + // signed + ASM_THUMB_CC_LT, + ASM_THUMB_CC_GT, + ASM_THUMB_CC_EQ, + ASM_THUMB_CC_LE, + ASM_THUMB_CC_GE, + ASM_THUMB_CC_NE, + }; + asm_thumb_bcc_rel9(emit->as, ops[op_idx], 6); + asm_thumb_mov_rlo_i8(emit->as, REG_RET, 0); + asm_thumb_b_rel12(emit->as, 4); + asm_thumb_mov_rlo_i8(emit->as, REG_RET, 1); + #endif #elif N_ARM asm_arm_cmp_reg_reg(emit->as, REG_ARG_2, reg_rhs); static uint ccs[6 + 6] = { From ec0503bd0ce3f851f15d79c69b660a6cf411ea78 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 21 Jan 2021 01:19:46 +1100 Subject: [PATCH 302/337] extmod/modonewire: Use pin_od_high/pin_od_low instead of pin_write. The pin is configured in open-drain mode so these od_high/od_low methods should be used. Signed-off-by: Damien George --- extmod/modonewire.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/extmod/modonewire.c b/extmod/modonewire.c index 210bee366e..6abe3dfad9 100644 --- a/extmod/modonewire.c +++ b/extmod/modonewire.c @@ -44,10 +44,10 @@ #define TIMING_WRITE3 (10) STATIC int onewire_bus_reset(mp_hal_pin_obj_t pin) { - mp_hal_pin_write(pin, 0); + mp_hal_pin_od_low(pin); mp_hal_delay_us(TIMING_RESET1); uint32_t i = mp_hal_quiet_timing_enter(); - mp_hal_pin_write(pin, 1); + mp_hal_pin_od_high(pin); mp_hal_delay_us_fast(TIMING_RESET2); int status = !mp_hal_pin_read(pin); mp_hal_quiet_timing_exit(i); @@ -56,11 +56,11 @@ STATIC int onewire_bus_reset(mp_hal_pin_obj_t pin) { } STATIC int onewire_bus_readbit(mp_hal_pin_obj_t pin) { - mp_hal_pin_write(pin, 1); + mp_hal_pin_od_high(pin); uint32_t i = mp_hal_quiet_timing_enter(); - mp_hal_pin_write(pin, 0); + mp_hal_pin_od_low(pin); mp_hal_delay_us_fast(TIMING_READ1); - mp_hal_pin_write(pin, 1); + mp_hal_pin_od_high(pin); mp_hal_delay_us_fast(TIMING_READ2); int value = mp_hal_pin_read(pin); mp_hal_quiet_timing_exit(i); @@ -70,13 +70,13 @@ STATIC int onewire_bus_readbit(mp_hal_pin_obj_t pin) { STATIC void onewire_bus_writebit(mp_hal_pin_obj_t pin, int value) { uint32_t i = mp_hal_quiet_timing_enter(); - mp_hal_pin_write(pin, 0); + mp_hal_pin_od_low(pin); mp_hal_delay_us_fast(TIMING_WRITE1); if (value) { - mp_hal_pin_write(pin, 1); + mp_hal_pin_od_high(pin); } mp_hal_delay_us_fast(TIMING_WRITE2); - mp_hal_pin_write(pin, 1); + mp_hal_pin_od_high(pin); mp_hal_delay_us_fast(TIMING_WRITE3); mp_hal_quiet_timing_exit(i); } From ef3ee7aa1005cd1f15c2144d4b46feb792ab3185 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 21 Jan 2021 18:04:39 +1100 Subject: [PATCH 303/337] lib/pico-sdk: Add new pico-sdk submodule, for the rp2 port. Signed-off-by: Damien George --- .gitmodules | 3 +++ lib/pico-sdk | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/pico-sdk diff --git a/.gitmodules b/.gitmodules index f4785955af..ceaa5342b4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -39,3 +39,6 @@ [submodule "lib/libhydrogen"] path = lib/libhydrogen url = https://github.com/jedisct1/libhydrogen.git +[submodule "lib/pico-sdk"] + path = lib/pico-sdk + url = https://github.com/raspberrypi/pico-sdk.git diff --git a/lib/pico-sdk b/lib/pico-sdk new file mode 160000 index 0000000000..493ed000dd --- /dev/null +++ b/lib/pico-sdk @@ -0,0 +1 @@ +Subproject commit 493ed000ddfc21aa1db933f5477dba52ac841aac From 469345e7285128739e2934e7934e107ffda79fc1 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 21 Jan 2021 00:34:08 +1100 Subject: [PATCH 304/337] rp2: Add new port to Raspberry Pi RP2 microcontroller. This commit adds a new port "rp2" which targets the new Raspberry Pi RP2040 microcontroller. The build system uses pure cmake (with a small Makefile wrapper for convenience). The USB driver is TinyUSB, and there is a machine module with most of the standard classes implemented. Some examples are provided in the examples/rp2/ directory. Work done in collaboration with Graham Sanderson. Signed-off-by: Damien George --- examples/rp2/pio_1hz.py | 35 ++ examples/rp2/pio_exec.py | 27 ++ examples/rp2/pio_pinchange.py | 46 ++ examples/rp2/pio_pwm.py | 45 ++ examples/rp2/pio_uart_tx.py | 44 ++ examples/rp2/pio_ws2812.py | 59 +++ examples/rp2/pwm_fade.py | 25 ++ ports/rp2/CMakeLists.txt | 162 +++++++ ports/rp2/Makefile | 14 + ports/rp2/README.md | 100 +++++ ports/rp2/machine_adc.c | 120 +++++ ports/rp2/machine_i2c.c | 161 +++++++ ports/rp2/machine_pin.c | 449 +++++++++++++++++++ ports/rp2/machine_pwm.c | 197 +++++++++ ports/rp2/machine_spi.c | 278 ++++++++++++ ports/rp2/machine_timer.c | 165 +++++++ ports/rp2/machine_uart.c | 246 +++++++++++ ports/rp2/machine_wdt.c | 78 ++++ ports/rp2/main.c | 208 +++++++++ ports/rp2/manifest.py | 3 + ports/rp2/memmap_mp.ld | 252 +++++++++++ ports/rp2/micropy_extmod.cmake | 40 ++ ports/rp2/micropy_py.cmake | 134 ++++++ ports/rp2/micropy_rules.cmake | 91 ++++ ports/rp2/modmachine.c | 101 +++++ ports/rp2/modmachine.h | 18 + ports/rp2/modrp2.c | 41 ++ ports/rp2/modrp2.h | 38 ++ ports/rp2/modules/_boot.py | 15 + ports/rp2/modules/rp2.py | 294 +++++++++++++ ports/rp2/moduos.c | 103 +++++ ports/rp2/modutime.c | 127 ++++++ ports/rp2/mpconfigport.h | 196 +++++++++ ports/rp2/mphalport.c | 142 ++++++ ports/rp2/mphalport.h | 113 +++++ ports/rp2/mpthreadport.c | 105 +++++ ports/rp2/mpthreadport.h | 64 +++ ports/rp2/qstrdefsport.h | 3 + ports/rp2/rp2_flash.c | 146 ++++++ ports/rp2/rp2_pio.c | 780 +++++++++++++++++++++++++++++++++ ports/rp2/tusb_config.h | 34 ++ ports/rp2/tusb_port.c | 115 +++++ ports/rp2/uart.c | 63 +++ ports/rp2/uart.h | 32 ++ 44 files changed, 5509 insertions(+) create mode 100644 examples/rp2/pio_1hz.py create mode 100644 examples/rp2/pio_exec.py create mode 100644 examples/rp2/pio_pinchange.py create mode 100644 examples/rp2/pio_pwm.py create mode 100644 examples/rp2/pio_uart_tx.py create mode 100644 examples/rp2/pio_ws2812.py create mode 100644 examples/rp2/pwm_fade.py create mode 100644 ports/rp2/CMakeLists.txt create mode 100644 ports/rp2/Makefile create mode 100644 ports/rp2/README.md create mode 100644 ports/rp2/machine_adc.c create mode 100644 ports/rp2/machine_i2c.c create mode 100644 ports/rp2/machine_pin.c create mode 100644 ports/rp2/machine_pwm.c create mode 100644 ports/rp2/machine_spi.c create mode 100644 ports/rp2/machine_timer.c create mode 100644 ports/rp2/machine_uart.c create mode 100644 ports/rp2/machine_wdt.c create mode 100644 ports/rp2/main.c create mode 100644 ports/rp2/manifest.py create mode 100644 ports/rp2/memmap_mp.ld create mode 100644 ports/rp2/micropy_extmod.cmake create mode 100644 ports/rp2/micropy_py.cmake create mode 100644 ports/rp2/micropy_rules.cmake create mode 100644 ports/rp2/modmachine.c create mode 100644 ports/rp2/modmachine.h create mode 100644 ports/rp2/modrp2.c create mode 100644 ports/rp2/modrp2.h create mode 100644 ports/rp2/modules/_boot.py create mode 100644 ports/rp2/modules/rp2.py create mode 100644 ports/rp2/moduos.c create mode 100644 ports/rp2/modutime.c create mode 100644 ports/rp2/mpconfigport.h create mode 100644 ports/rp2/mphalport.c create mode 100644 ports/rp2/mphalport.h create mode 100644 ports/rp2/mpthreadport.c create mode 100644 ports/rp2/mpthreadport.h create mode 100644 ports/rp2/qstrdefsport.h create mode 100644 ports/rp2/rp2_flash.c create mode 100644 ports/rp2/rp2_pio.c create mode 100644 ports/rp2/tusb_config.h create mode 100644 ports/rp2/tusb_port.c create mode 100644 ports/rp2/uart.c create mode 100644 ports/rp2/uart.h diff --git a/examples/rp2/pio_1hz.py b/examples/rp2/pio_1hz.py new file mode 100644 index 0000000000..c18aa22fc0 --- /dev/null +++ b/examples/rp2/pio_1hz.py @@ -0,0 +1,35 @@ +# Example using PIO to blink an LED and raise an IRQ at 1Hz. + +import time +from machine import Pin +import rp2 + + +@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW) +def blink_1hz(): + # fmt: off + # Cycles: 1 + 1 + 6 + 32 * (30 + 1) = 1000 + irq(rel(0)) + set(pins, 1) + set(x, 31) [5] + label("delay_high") + nop() [29] + jmp(x_dec, "delay_high") + + # Cycles: 1 + 7 + 32 * (30 + 1) = 1000 + set(pins, 0) + set(x, 31) [6] + label("delay_low") + nop() [29] + jmp(x_dec, "delay_low") + # fmt: on + + +# Create the StateMachine with the blink_1hz program, outputting on Pin(25). +sm = rp2.StateMachine(0, blink_1hz, freq=2000, set_base=Pin(25)) + +# Set the IRQ handler to print the millisecond timestamp. +sm.irq(lambda p: print(time.ticks_ms())) + +# Start the StateMachine. +sm.active(1) diff --git a/examples/rp2/pio_exec.py b/examples/rp2/pio_exec.py new file mode 100644 index 0000000000..d8cbc33ef0 --- /dev/null +++ b/examples/rp2/pio_exec.py @@ -0,0 +1,27 @@ +# Example using PIO to turn on an LED via an explicit exec. +# +# Demonstrates: +# - using set_init and set_base +# - using StateMachine.exec + +import time +from machine import Pin +import rp2 + +# Define an empty program that uses a single set pin. +@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW) +def prog(): + pass + + +# Construct the StateMachine, binding Pin(25) to the set pin. +sm = rp2.StateMachine(0, prog, set_base=Pin(25)) + +# Turn on the set pin via an exec instruction. +sm.exec("set(pins, 1)") + +# Sleep for 500ms. +time.sleep(0.5) + +# Turn off the set pin via an exec instruction. +sm.exec("set(pins, 0)") diff --git a/examples/rp2/pio_pinchange.py b/examples/rp2/pio_pinchange.py new file mode 100644 index 0000000000..767c8e78c2 --- /dev/null +++ b/examples/rp2/pio_pinchange.py @@ -0,0 +1,46 @@ +# Example using PIO to wait for a pin change and raise an IRQ. +# +# Demonstrates: +# - PIO wrapping +# - PIO wait instruction, waiting on an input pin +# - PIO irq instruction, in blocking mode with relative IRQ number +# - setting the in_base pin for a StateMachine +# - setting an irq handler for a StateMachine +# - instantiating 2x StateMachine's with the same program and different pins + +import time +from machine import Pin +import rp2 + + +@rp2.asm_pio() +def wait_pin_low(): + wrap_target() + + wait(0, pin, 0) + irq(block, rel(0)) + wait(1, pin, 0) + + wrap() + + +def handler(sm): + # Print a (wrapping) timestamp, and the state machine object. + print(time.ticks_ms(), sm) + + +# Instantiate StateMachine(0) with wait_pin_low program on Pin(16). +pin16 = Pin(16, Pin.IN, Pin.PULL_UP) +sm0 = rp2.StateMachine(0, wait_pin_low, in_base=pin16) +sm0.irq(handler) + +# Instantiate StateMachine(1) with wait_pin_low program on Pin(17). +pin17 = Pin(17, Pin.IN, Pin.PULL_UP) +sm1 = rp2.StateMachine(1, wait_pin_low, in_base=pin17) +sm1.irq(handler) + +# Start the StateMachine's running. +sm0.active(1) +sm1.active(1) + +# Now, when Pin(16) or Pin(17) is pulled low a message will be printed to the REPL. diff --git a/examples/rp2/pio_pwm.py b/examples/rp2/pio_pwm.py new file mode 100644 index 0000000000..8e87448ca8 --- /dev/null +++ b/examples/rp2/pio_pwm.py @@ -0,0 +1,45 @@ +# Example of using PIO for PWM, and fading the brightness of an LED + +from machine import Pin +from rp2 import PIO, StateMachine, asm_pio +from time import sleep + + +@asm_pio(sideset_init=PIO.OUT_LOW) +def pwm_prog(): + # fmt: off + pull(noblock) .side(0) + mov(x, osr) # Keep most recent pull data stashed in X, for recycling by noblock + mov(y, isr) # ISR must be preloaded with PWM count max + label("pwmloop") + jmp(x_not_y, "skip") + nop() .side(1) + label("skip") + jmp(y_dec, "pwmloop") + # fmt: on + + +class PIOPWM: + def __init__(self, sm_id, pin, max_count, count_freq): + self._sm = StateMachine(sm_id, pwm_prog, freq=2 * count_freq, sideset_base=Pin(pin)) + # Use exec() to load max count into ISR + self._sm.put(max_count) + self._sm.exec("pull()") + self._sm.exec("mov(isr, osr)") + self._sm.active(1) + self._max_count = max_count + + def set(self, value): + # Minimum value is -1 (completely turn off), 0 actually still produces narrow pulse + value = max(value, -1) + value = min(value, self._max_count) + self._sm.put(value) + + +# Pin 25 is LED on Pico boards +pwm = PIOPWM(0, 25, max_count=(1 << 16) - 1, count_freq=10_000_000) + +while True: + for i in range(256): + pwm.set(i ** 2) + sleep(0.01) diff --git a/examples/rp2/pio_uart_tx.py b/examples/rp2/pio_uart_tx.py new file mode 100644 index 0000000000..0f8c1260bd --- /dev/null +++ b/examples/rp2/pio_uart_tx.py @@ -0,0 +1,44 @@ +# Example using PIO to create a UART TX interface + +from machine import Pin +from rp2 import PIO, StateMachine, asm_pio + +UART_BAUD = 115200 +PIN_BASE = 10 +NUM_UARTS = 8 + + +@asm_pio(sideset_init=PIO.OUT_HIGH, out_init=PIO.OUT_HIGH, out_shiftdir=PIO.SHIFT_RIGHT) +def uart_tx(): + # fmt: off + # Block with TX deasserted until data available + pull() + # Initialise bit counter, assert start bit for 8 cycles + set(x, 7) .side(0) [7] + # Shift out 8 data bits, 8 execution cycles per bit + label("bitloop") + out(pins, 1) [6] + jmp(x_dec, "bitloop") + # Assert stop bit for 8 cycles total (incl 1 for pull()) + nop() .side(1) [6] + # fmt: on + + +# Now we add 8 UART TXs, on pins 10 to 17. Use the same baud rate for all of them. +uarts = [] +for i in range(NUM_UARTS): + sm = StateMachine( + i, uart_tx, freq=8 * UART_BAUD, sideset_base=Pin(PIN_BASE + i), out_base=Pin(PIN_BASE + i) + ) + sm.active(1) + uarts.append(sm) + +# We can print characters from each UART by pushing them to the TX FIFO +def pio_uart_print(sm, s): + for c in s: + sm.put(ord(c)) + + +# Print a different message from each UART +for i, u in enumerate(uarts): + pio_uart_print(u, "Hello from UART {}!\n".format(i)) diff --git a/examples/rp2/pio_ws2812.py b/examples/rp2/pio_ws2812.py new file mode 100644 index 0000000000..dd021a9d56 --- /dev/null +++ b/examples/rp2/pio_ws2812.py @@ -0,0 +1,59 @@ +# Example using PIO to drive a set of WS2812 LEDs. + +import array, time +from machine import Pin +import rp2 + +# Configure the number of WS2812 LEDs. +NUM_LEDS = 8 + + +@rp2.asm_pio( + sideset_init=rp2.PIO.OUT_LOW, + out_shiftdir=rp2.PIO.SHIFT_LEFT, + autopull=True, + pull_thresh=24, +) +def ws2812(): + # fmt: off + T1 = 2 + T2 = 5 + T3 = 3 + wrap_target() + label("bitloop") + out(x, 1) .side(0) [T3 - 1] + jmp(not_x, "do_zero") .side(1) [T1 - 1] + jmp("bitloop") .side(1) [T2 - 1] + label("do_zero") + nop() .side(0) [T2 - 1] + wrap() + # fmt: on + + +# Create the StateMachine with the ws2812 program, outputting on Pin(22). +sm = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(22)) + +# Start the StateMachine, it will wait for data on its FIFO. +sm.active(1) + +# Display a pattern on the LEDs via an array of LED RGB values. +ar = array.array("I", [0 for _ in range(NUM_LEDS)]) + +# Cycle colours. +for i in range(4 * NUM_LEDS): + for j in range(NUM_LEDS): + r = j * 100 // (NUM_LEDS - 1) + b = 100 - j * 100 // (NUM_LEDS - 1) + if j != i % NUM_LEDS: + r >>= 3 + b >>= 3 + ar[j] = r << 16 | b + sm.put(ar, 8) + time.sleep_ms(50) + +# Fade out. +for i in range(24): + for j in range(NUM_LEDS): + ar[j] = ar[j] >> 1 & 0x7F7F7F + sm.put(ar, 8) + time.sleep_ms(50) diff --git a/examples/rp2/pwm_fade.py b/examples/rp2/pwm_fade.py new file mode 100644 index 0000000000..7264edaa29 --- /dev/null +++ b/examples/rp2/pwm_fade.py @@ -0,0 +1,25 @@ +# Example using PWM to fade an LED. + +import time +from machine import Pin, PWM + + +# Construct PWM object, with LED on Pin(25). +pwm = PWM(Pin(25)) + +# Set the PWM frequency. +pwm.freq(1000) + +# Fade the LED in and out a few times. +duty = 0 +direction = 1 +for _ in range(8 * 256): + duty += direction + if duty > 255: + duty = 255 + direction = -1 + elif duty < 0: + duty = 0 + direction = 1 + pwm.duty_u16(duty * duty) + time.sleep(0.001) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt new file mode 100644 index 0000000000..400cb8480e --- /dev/null +++ b/ports/rp2/CMakeLists.txt @@ -0,0 +1,162 @@ +cmake_minimum_required(VERSION 3.12) + +# Set build type to reduce firmware size +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE MinSizeRel) +endif() + +# Set main target and component locations +set(MICROPYTHON_TARGET firmware) +get_filename_component(MPY_DIR "../.." ABSOLUTE) +if (PICO_SDK_PATH_OVERRIDE) + set(PICO_SDK_PATH ${PICO_SDK_PATH_OVERRIDE}) +else() + set(PICO_SDK_PATH ../../lib/pico-sdk) +endif() + +# Include component cmake fragments +include(micropy_py.cmake) +include(micropy_extmod.cmake) +include(${PICO_SDK_PATH}/pico_sdk_init.cmake) + +# Define the top-level project +project(${MICROPYTHON_TARGET}) + +pico_sdk_init() + +add_executable(${MICROPYTHON_TARGET}) + +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/mp-readline/readline.c + ${MPY_DIR}/lib/oofatfs/ff.c + ${MPY_DIR}/lib/oofatfs/ffunicode.c + ${MPY_DIR}/lib/timeutils/timeutils.c + ${MPY_DIR}/lib/utils/gchelper_m0.s + ${MPY_DIR}/lib/utils/gchelper_native.c + ${MPY_DIR}/lib/utils/mpirq.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 +) + +set(SOURCE_PORT + machine_adc.c + machine_i2c.c + machine_pin.c + machine_pwm.c + machine_spi.c + machine_timer.c + machine_uart.c + machine_wdt.c + main.c + modmachine.c + modrp2.c + moduos.c + modutime.c + mphalport.c + mpthreadport.c + rp2_flash.c + rp2_pio.c + tusb_port.c + uart.c +) + +set(SOURCE_QSTR + ${SOURCE_PY} + ${SOURCE_EXTMOD} + ${MPY_DIR}/lib/utils/mpirq.c + ${MPY_DIR}/lib/utils/sys_stdio_mphal.c + ${PROJECT_SOURCE_DIR}/machine_adc.c + ${PROJECT_SOURCE_DIR}/machine_i2c.c + ${PROJECT_SOURCE_DIR}/machine_pin.c + ${PROJECT_SOURCE_DIR}/machine_pwm.c + ${PROJECT_SOURCE_DIR}/machine_spi.c + ${PROJECT_SOURCE_DIR}/machine_timer.c + ${PROJECT_SOURCE_DIR}/machine_uart.c + ${PROJECT_SOURCE_DIR}/machine_wdt.c + ${PROJECT_SOURCE_DIR}/modmachine.c + ${PROJECT_SOURCE_DIR}/modrp2.c + ${PROJECT_SOURCE_DIR}/moduos.c + ${PROJECT_SOURCE_DIR}/modutime.c + ${PROJECT_SOURCE_DIR}/rp2_flash.c + ${PROJECT_SOURCE_DIR}/rp2_pio.c +) + +set(MPY_QSTR_DEFS ${PROJECT_SOURCE_DIR}/qstrdefsport.h) + +# Define mpy-cross flags and frozen manifest +set(MPY_CROSS_FLAGS -march=armv7m) +set(FROZEN_MANIFEST ${PROJECT_SOURCE_DIR}/manifest.py) + +include(micropy_rules.cmake) + +target_sources(${MICROPYTHON_TARGET} PRIVATE + ${SOURCE_PY} + ${SOURCE_EXTMOD} + ${SOURCE_LIB} + ${SOURCE_DRIVERS} + ${SOURCE_PORT} +) + +target_include_directories(${MICROPYTHON_TARGET} PRIVATE + "${PROJECT_SOURCE_DIR}" + "${MPY_DIR}" + "${CMAKE_BINARY_DIR}" + ) + +target_compile_options(${MICROPYTHON_TARGET} PRIVATE + -Wall + #-Werror + -DFFCONF_H=\"${MPY_DIR}/lib/oofatfs/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 +) + +target_compile_definitions(${MICROPYTHON_TARGET} PRIVATE + PICO_FLOAT_PROPAGATE_NANS=1 + PICO_STACK_SIZE=0x2000 + PICO_CORE1_STACK_SIZE=0 + PICO_PROGRAM_NAME="MicroPython" + PICO_NO_PROGRAM_VERSION_STRING=1 # do it ourselves in main.c + MICROPY_BUILD_TYPE="${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION} ${CMAKE_BUILD_TYPE}" + PICO_NO_BI_STDIO_UART=1 # we call it UART REPL +) + +target_link_libraries(${MICROPYTHON_TARGET} + hardware_adc + hardware_dma + hardware_flash + hardware_i2c + hardware_pio + hardware_pwm + hardware_rtc + hardware_spi + hardware_sync + pico_multicore + pico_stdlib_headers + pico_stdlib + tinyusb_device +) + +# todo this is a bit brittle, but we want to move a few source files into RAM (which requires +# a linker script modification) until we explicitly add macro calls around the function +# defs to move them into RAM. +if (PICO_ON_DEVICE AND NOT PICO_NO_FLASH AND NOT PICO_COPY_TO_RAM) + pico_set_linker_script(${MICROPYTHON_TARGET} ${CMAKE_CURRENT_LIST_DIR}/memmap_mp.ld) +endif() + +pico_add_extra_outputs(${MICROPYTHON_TARGET}) + +add_custom_command(TARGET ${MICROPYTHON_TARGET} + POST_BUILD + COMMAND arm-none-eabi-size --format=berkeley ${PROJECT_BINARY_DIR}/${MICROPYTHON_TARGET}.elf + VERBATIM +) diff --git a/ports/rp2/Makefile b/ports/rp2/Makefile new file mode 100644 index 0000000000..08cd53dcca --- /dev/null +++ b/ports/rp2/Makefile @@ -0,0 +1,14 @@ +# Makefile for micropython on Raspberry Pi RP2 +# +# This is a simple wrapper around cmake + +BUILD = build + +$(VERBOSE)MAKESILENT = -s + +all: + [ -d $(BUILD) ] || cmake -S . -B $(BUILD) -DPICO_BUILD_DOCS=0 + $(MAKE) $(MAKESILENT) -C $(BUILD) + +clean: + $(RM) -rf $(BUILD) diff --git a/ports/rp2/README.md b/ports/rp2/README.md new file mode 100644 index 0000000000..d49550c5c4 --- /dev/null +++ b/ports/rp2/README.md @@ -0,0 +1,100 @@ +# The RP2 port + +This is a port of MicroPython to the Raspberry Pi RP2 series of microcontrollers. +Currently supported features are: + +- REPL over USB VCP, and optionally over UART (on GP0/GP1). +- Filesystem on the internal flash, using littlefs2. +- Support for native code generation and inline assembler. +- `utime` module with sleep, time and ticks functions. +- `uos` module with VFS support. +- `machine` module with the following classes: `Pin`, `ADC`, `PWM`, `I2C`, `SPI`, + `SoftI2C`, `SoftSPI`, `Timer`, `UART`, `WDT`. +- `rp2` module with programmable IO (PIO) support. + +See the `examples/rp2/` directory for some example code. + +## Building + +The MicroPython cross-compiler must be built first, which will be used to +pre-compile (freeze) built-in Python code. This cross-compiler is built and +run on the host machine using: + + $ make -C mpy-cross + +This command should be executed from the root directory of this repository. +All other commands below should be executed from the ports/rp2/ directory. + +Building of the RP2 firmware is done entirely using CMake, although a simple +Makefile is also provided as a convenience. To build the firmware run (from +this directory): + + $ make clean + $ make + +You can also build the standard CMake way. The final firmware is found in +the top-level of the CMake build directory (`build` by default) and is +called `firmware.uf2`. + +## Deploying firmware to the device + +Firmware can be deployed to the device by putting it into bootloader mode +(hold down BOOTSEL while powering on or resetting) and then copying +`firmware.uf2` to the USB mass storage device that appears. + +If MicroPython is already installed then the bootloader can be entered by +executing `import machine; machine.bootloader()` at the REPL. + +## Sample code + +The following samples can be easily run on the board by entering paste mode +with Ctrl-E at the REPL, then cut-and-pasting the sample code to the REPL, then +executing the code with Ctrl-D. + +### Blinky + +This blinks the on-board LED on the Pico board at 1.25Hz, using a Timer object +with a callback. + +```python +from machine import Pin, Timer +led = Pin(25, Pin.OUT) +tim = Timer() +def tick(timer): + global led + led.toggle() + +tim.init(freq=2.5, mode=Timer.PERIODIC, callback=tick) +``` + +### PIO blinky + +This blinks the on-board LED on the Pico board at 1Hz, using a PIO peripheral and +PIO assembler to directly toggle the LED at the required rate. + +```python +from machine import Pin +import rp2 + +@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW) +def blink_1hz(): + # Turn on the LED and delay, taking 1000 cycles. + set(pins, 1) + set(x, 31) [6] + label("delay_high") + nop() [29] + jmp(x_dec, "delay_high") + + # Turn off the LED and delay, taking 1000 cycles. + set(pins, 0) + set(x, 31) [6] + label("delay_low") + nop() [29] + jmp(x_dec, "delay_low") + +# Create StateMachine(0) with the blink_1hz program, outputting on Pin(25). +sm = rp2.StateMachine(0, blink_1hz, freq=2000, set_base=Pin(25)) +sm.active(1) +``` + +See the `examples/rp2/` directory for further example code. diff --git a/ports/rp2/machine_adc.c b/ports/rp2/machine_adc.c new file mode 100644 index 0000000000..f0f367151c --- /dev/null +++ b/ports/rp2/machine_adc.c @@ -0,0 +1,120 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 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/runtime.h" +#include "py/mphal.h" +#include "hardware/adc.h" + +#define ADC_IS_VALID_GPIO(gpio) ((gpio) >= 26 && (gpio) <= 29) +#define ADC_CHANNEL_FROM_GPIO(gpio) ((gpio) - 26) +#define ADC_CHANNEL_TEMPSENSOR (4) + +STATIC uint16_t adc_config_and_read_u16(uint32_t channel) { + adc_select_input(channel); + uint32_t raw = adc_read(); + const uint32_t bits = 12; + // Scale raw reading to 16 bit value using a Taylor expansion (for 8 <= bits <= 16) + return raw << (16 - bits) | raw >> (2 * bits - 16); +} + +/******************************************************************************/ +// MicroPython bindings for machine.ADC + +const mp_obj_type_t machine_adc_type; + +typedef struct _machine_adc_obj_t { + mp_obj_base_t base; + uint32_t channel; +} machine_adc_obj_t; + +STATIC void machine_adc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", self->channel); +} + +// ADC(id) +STATIC mp_obj_t machine_adc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + // Check number of arguments + mp_arg_check_num(n_args, n_kw, 1, 1, false); + + mp_obj_t source = all_args[0]; + + uint32_t channel; + if (mp_obj_is_int(source)) { + // Get and validate channel number. + channel = mp_obj_get_int(source); + if (!((channel >= 0 && channel <= ADC_CHANNEL_TEMPSENSOR) || ADC_IS_VALID_GPIO(channel))) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid channel")); + } + + } else { + // Get GPIO and check it has ADC capabilities. + channel = mp_hal_get_pin_obj(source); + if (!ADC_IS_VALID_GPIO(channel)) { + mp_raise_ValueError(MP_ERROR_TEXT("Pin doesn't have ADC capabilities")); + } + } + + adc_init(); + + if (ADC_IS_VALID_GPIO(channel)) { + // Configure the GPIO pin in ADC mode. + adc_gpio_init(channel); + channel = ADC_CHANNEL_FROM_GPIO(channel); + } else if (channel == ADC_CHANNEL_TEMPSENSOR) { + // Enable temperature sensor. + adc_set_temp_sensor_enabled(1); + } + + // Create ADC object. + machine_adc_obj_t *o = m_new_obj(machine_adc_obj_t); + o->base.type = &machine_adc_type; + o->channel = channel; + + return MP_OBJ_FROM_PTR(o); +} + +// read_u16() +STATIC mp_obj_t machine_adc_read_u16(mp_obj_t self_in) { + machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_NEW_SMALL_INT(adc_config_and_read_u16(self->channel)); +} +MP_DEFINE_CONST_FUN_OBJ_1(machine_adc_read_u16_obj, machine_adc_read_u16); + +STATIC const mp_rom_map_elem_t machine_adc_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_read_u16), MP_ROM_PTR(&machine_adc_read_u16_obj) }, + + { MP_ROM_QSTR(MP_QSTR_CORE_TEMP), MP_ROM_INT(ADC_CHANNEL_TEMPSENSOR) }, +}; +STATIC MP_DEFINE_CONST_DICT(machine_adc_locals_dict, machine_adc_locals_dict_table); + +const mp_obj_type_t machine_adc_type = { + { &mp_type_type }, + .name = MP_QSTR_ADC, + .print = machine_adc_print, + .make_new = machine_adc_make_new, + .locals_dict = (mp_obj_dict_t *)&machine_adc_locals_dict, +}; diff --git a/ports/rp2/machine_i2c.c b/ports/rp2/machine_i2c.c new file mode 100644 index 0000000000..0852cc1993 --- /dev/null +++ b/ports/rp2/machine_i2c.c @@ -0,0 +1,161 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 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/runtime.h" +#include "py/mphal.h" +#include "py/mperrno.h" +#include "extmod/machine_i2c.h" +#include "modmachine.h" + +#include "hardware/i2c.h" + +#define DEFAULT_I2C_FREQ (400000) +#define DEFAULT_I2C0_SCL (9) +#define DEFAULT_I2C0_SDA (8) +#define DEFAULT_I2C1_SCL (7) +#define DEFAULT_I2C1_SDA (6) + +// SDA/SCL on even/odd pins, I2C0/I2C1 on even/odd pairs of pins. +#define IS_VALID_SCL(i2c, pin) (((pin) & 1) == 1 && (((pin) & 2) >> 1) == (i2c)) +#define IS_VALID_SDA(i2c, pin) (((pin) & 1) == 0 && (((pin) & 2) >> 1) == (i2c)) + +typedef struct _machine_i2c_obj_t { + mp_obj_base_t base; + i2c_inst_t *const i2c_inst; + uint8_t i2c_id; + uint8_t scl; + uint8_t sda; + uint32_t freq; +} machine_i2c_obj_t; + +STATIC machine_i2c_obj_t machine_i2c_obj[] = { + {{&machine_hw_i2c_type}, i2c0, 0, DEFAULT_I2C0_SCL, DEFAULT_I2C0_SDA, 0}, + {{&machine_hw_i2c_type}, i2c1, 1, DEFAULT_I2C1_SCL, DEFAULT_I2C1_SDA, 0}, +}; + +STATIC void machine_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "I2C(%u, freq=%u, scl=%u, sda=%u)", + self->i2c_id, self->freq, self->scl, self->sda); +} + +mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_id, ARG_freq, ARG_scl, ARG_sda }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_freq, MP_ARG_INT, {.u_int = DEFAULT_I2C_FREQ} }, + { MP_QSTR_scl, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_sda, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Get I2C bus. + int i2c_id = mp_obj_get_int(args[ARG_id].u_obj); + if (i2c_id < 0 || i2c_id >= MP_ARRAY_SIZE(machine_i2c_obj)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't exist"), i2c_id); + } + + // Get static peripheral object. + machine_i2c_obj_t *self = (machine_i2c_obj_t *)&machine_i2c_obj[i2c_id]; + + // Set SCL/SDA pins if configured. + if (args[ARG_scl].u_obj != mp_const_none) { + int scl = mp_hal_get_pin_obj(args[ARG_scl].u_obj); + if (!IS_VALID_SCL(self->i2c_id, scl)) { + mp_raise_ValueError(MP_ERROR_TEXT("bad SCL pin")); + } + self->scl = scl; + } + if (args[ARG_sda].u_obj != mp_const_none) { + int sda = mp_hal_get_pin_obj(args[ARG_sda].u_obj); + if (!IS_VALID_SDA(self->i2c_id, sda)) { + mp_raise_ValueError(MP_ERROR_TEXT("bad SDA pin")); + } + self->sda = sda; + } + + // Initialise the I2C peripheral if any arguments given, or it was not initialised previously. + if (n_args > 1 || n_kw > 0 || self->freq == 0) { + self->freq = args[ARG_freq].u_int; + i2c_init(self->i2c_inst, self->freq); + self->freq = i2c_set_baudrate(self->i2c_inst, self->freq); + gpio_set_function(self->scl, GPIO_FUNC_I2C); + gpio_set_function(self->sda, GPIO_FUNC_I2C); + gpio_set_pulls(self->scl, true, 0); + gpio_set_pulls(self->sda, true, 0); + } + + return MP_OBJ_FROM_PTR(self); +} + +STATIC int machine_i2c_transfer_single(mp_obj_base_t *self_in, uint16_t addr, size_t len, uint8_t *buf, unsigned int flags) { + machine_i2c_obj_t *self = (machine_i2c_obj_t *)self_in; + int ret; + bool nostop = !(flags & MP_MACHINE_I2C_FLAG_STOP); + if (flags & MP_MACHINE_I2C_FLAG_READ) { + ret = i2c_read_blocking(self->i2c_inst, addr, buf, len, nostop); + } else { + if (len <= 2) { + // Workaround issue with hardware I2C not accepting short writes. + mp_machine_soft_i2c_obj_t soft_i2c = { + .base = { &mp_machine_soft_i2c_type }, + .us_delay = 500000 / self->freq + 1, + .us_timeout = 255, + .scl = self->scl, + .sda = self->sda, + }; + mp_machine_i2c_buf_t bufs = { + .len = len, + .buf = buf, + }; + mp_hal_pin_open_drain(self->scl); + mp_hal_pin_open_drain(self->sda); + ret = mp_machine_soft_i2c_transfer(&soft_i2c.base, addr, 1, &bufs, flags); + gpio_set_function(self->scl, GPIO_FUNC_I2C); + gpio_set_function(self->sda, GPIO_FUNC_I2C); + } else { + ret = i2c_write_blocking(self->i2c_inst, addr, buf, len, nostop); + } + } + return (ret < 0) ? -MP_EIO : ret; +} + +STATIC const mp_machine_i2c_p_t machine_i2c_p = { + .transfer = mp_machine_i2c_transfer_adaptor, + .transfer_single = machine_i2c_transfer_single, +}; + +const mp_obj_type_t machine_hw_i2c_type = { + { &mp_type_type }, + .name = MP_QSTR_I2C, + .print = machine_i2c_print, + .make_new = machine_i2c_make_new, + .protocol = &machine_i2c_p, + .locals_dict = (mp_obj_dict_t *)&mp_machine_i2c_locals_dict, +}; diff --git a/ports/rp2/machine_pin.c b/ports/rp2/machine_pin.c new file mode 100644 index 0000000000..f798c10fdf --- /dev/null +++ b/ports/rp2/machine_pin.c @@ -0,0 +1,449 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016-2021 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include "py/runtime.h" +#include "py/mphal.h" +#include "lib/utils/mpirq.h" +#include "modmachine.h" +#include "extmod/virtpin.h" + +#include "hardware/irq.h" +#include "hardware/regs/intctrl.h" +#include "hardware/structs/iobank0.h" +#include "hardware/structs/padsbank0.h" + +#define GPIO_MODE_IN (0) +#define GPIO_MODE_OUT (1) +#define GPIO_MODE_OPEN_DRAIN (2) +#define GPIO_MODE_ALT (3) + +// These can be or'd together. +#define GPIO_PULL_UP (1) +#define GPIO_PULL_DOWN (2) + +#define GPIO_IRQ_ALL (0xf) + +// Macros to access the state of the hardware. +#define GPIO_GET_FUNCSEL(id) ((iobank0_hw->io[(id)].ctrl & IO_BANK0_GPIO0_CTRL_FUNCSEL_BITS) >> IO_BANK0_GPIO0_CTRL_FUNCSEL_LSB) +#define GPIO_IS_OUT(id) (sio_hw->gpio_oe & (1 << (id))) +#define GPIO_IS_PULL_UP(id) (padsbank0_hw->io[(id)] & PADS_BANK0_GPIO0_PUE_BITS) +#define GPIO_IS_PULL_DOWN(id) (padsbank0_hw->io[(id)] & PADS_BANK0_GPIO0_PDE_BITS) + +// Open drain behaviour is simulated. +#define GPIO_IS_OPEN_DRAIN(id) (machine_pin_open_drain_mask & (1 << (id))) + +typedef struct _machine_pin_obj_t { + mp_obj_base_t base; + uint32_t id; +} machine_pin_obj_t; + +typedef struct _machine_pin_irq_obj_t { + mp_irq_obj_t base; + uint32_t flags; + uint32_t trigger; +} machine_pin_irq_obj_t; + +STATIC const mp_irq_methods_t machine_pin_irq_methods; + +STATIC const machine_pin_obj_t machine_pin_obj[N_GPIOS] = { + {{&machine_pin_type}, 0}, + {{&machine_pin_type}, 1}, + {{&machine_pin_type}, 2}, + {{&machine_pin_type}, 3}, + {{&machine_pin_type}, 4}, + {{&machine_pin_type}, 5}, + {{&machine_pin_type}, 6}, + {{&machine_pin_type}, 7}, + {{&machine_pin_type}, 8}, + {{&machine_pin_type}, 9}, + {{&machine_pin_type}, 10}, + {{&machine_pin_type}, 11}, + {{&machine_pin_type}, 12}, + {{&machine_pin_type}, 13}, + {{&machine_pin_type}, 14}, + {{&machine_pin_type}, 15}, + {{&machine_pin_type}, 16}, + {{&machine_pin_type}, 17}, + {{&machine_pin_type}, 18}, + {{&machine_pin_type}, 19}, + {{&machine_pin_type}, 20}, + {{&machine_pin_type}, 21}, + {{&machine_pin_type}, 22}, + {{&machine_pin_type}, 23}, + {{&machine_pin_type}, 24}, + {{&machine_pin_type}, 25}, + {{&machine_pin_type}, 26}, + {{&machine_pin_type}, 27}, + {{&machine_pin_type}, 28}, + {{&machine_pin_type}, 29}, +}; + +// Mask with "1" indicating that the corresponding pin is in simulated open-drain mode. +uint32_t machine_pin_open_drain_mask; + +STATIC void gpio_irq(void) { + for (int i = 0; i < 4; ++i) { + uint32_t intr = iobank0_hw->intr[i]; + if (intr) { + for (int j = 0; j < 8; ++j) { + if (intr & 0xf) { + uint32_t gpio = 8 * i + j; + gpio_acknowledge_irq(gpio, intr & 0xf); + machine_pin_irq_obj_t *irq = MP_STATE_PORT(machine_pin_irq_obj[gpio]); + if (irq != NULL && (intr & irq->trigger)) { + irq->flags = intr & irq->trigger; + mp_irq_handler(&irq->base); + } + } + intr >>= 4; + } + } + } +} + +void machine_pin_init(void) { + memset(MP_STATE_PORT(machine_pin_irq_obj), 0, sizeof(MP_STATE_PORT(machine_pin_irq_obj))); + irq_set_exclusive_handler(IO_IRQ_BANK0, gpio_irq); + irq_set_enabled(IO_IRQ_BANK0, true); +} + +void machine_pin_deinit(void) { + for (int i = 0; i < N_GPIOS; ++i) { + gpio_set_irq_enabled(i, GPIO_IRQ_ALL, false); + } + irq_set_enabled(IO_IRQ_BANK0, false); + irq_remove_handler(IO_IRQ_BANK0, gpio_irq); +} + +STATIC void machine_pin_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_pin_obj_t *self = self_in; + uint funcsel = GPIO_GET_FUNCSEL(self->id); + qstr mode_qst; + if (funcsel == GPIO_FUNC_SIO) { + if (GPIO_IS_OPEN_DRAIN(self->id)) { + mode_qst = MP_QSTR_OPEN_DRAIN; + } else if (GPIO_IS_OUT(self->id)) { + mode_qst = MP_QSTR_OUT; + } else { + mode_qst = MP_QSTR_IN; + } + } else { + mode_qst = MP_QSTR_ALT; + } + mp_printf(print, "Pin(%u, mode=%q", self->id, mode_qst); + bool pull_up = false; + if (GPIO_IS_PULL_UP(self->id)) { + mp_printf(print, ", pull=%q", MP_QSTR_PULL_UP); + pull_up = true; + } + if (GPIO_IS_PULL_DOWN(self->id)) { + if (pull_up) { + mp_printf(print, "|%q", MP_QSTR_PULL_DOWN); + } else { + mp_printf(print, ", pull=%q", MP_QSTR_PULL_DOWN); + } + } + if (funcsel != GPIO_FUNC_SIO) { + mp_printf(print, ", alt=%u", funcsel); + } + mp_printf(print, ")"); +} + +// pin.init(mode, pull=None, *, value=None, alt=FUNC_SIO) +STATIC mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_mode, ARG_pull, ARG_value, ARG_alt }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_mode, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE}}, + { MP_QSTR_pull, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE}}, + { MP_QSTR_value, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE}}, + { MP_QSTR_alt, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = GPIO_FUNC_SIO}}, + }; + + // parse args + 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); + + // set initial value (do this before configuring mode/pull) + if (args[ARG_value].u_obj != mp_const_none) { + gpio_put(self->id, mp_obj_is_true(args[ARG_value].u_obj)); + } + + // configure mode + if (args[ARG_mode].u_obj != mp_const_none) { + mp_int_t mode = mp_obj_get_int(args[ARG_mode].u_obj); + if (mode == GPIO_MODE_IN) { + mp_hal_pin_input(self->id); + } else if (mode == GPIO_MODE_OUT) { + mp_hal_pin_output(self->id); + } else if (mode == GPIO_MODE_OPEN_DRAIN) { + mp_hal_pin_open_drain(self->id); + } else { + // Alternate function. + gpio_set_function(self->id, args[ARG_alt].u_int); + machine_pin_open_drain_mask &= ~(1 << self->id); + } + } + + // configure pull (unconditionally because None means no-pull) + uint32_t pull = 0; + if (args[ARG_pull].u_obj != mp_const_none) { + pull = mp_obj_get_int(args[ARG_pull].u_obj); + } + gpio_set_pulls(self->id, pull & GPIO_PULL_UP, pull & GPIO_PULL_DOWN); + + return mp_const_none; +} + +// constructor(id, ...) +mp_obj_t mp_pin_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + + // get the wanted pin object + int wanted_pin = mp_obj_get_int(args[0]); + if (!(0 <= wanted_pin && wanted_pin < MP_ARRAY_SIZE(machine_pin_obj))) { + mp_raise_ValueError("invalid pin"); + } + const machine_pin_obj_t *self = &machine_pin_obj[wanted_pin]; + + if (n_args > 1 || n_kw > 0) { + // pin mode given, so configure this GPIO + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + machine_pin_obj_init_helper(self, n_args - 1, args + 1, &kw_args); + } + + return MP_OBJ_FROM_PTR(self); +} + +// fast method for getting/setting pin value +STATIC mp_obj_t machine_pin_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 0, 1, false); + machine_pin_obj_t *self = self_in; + if (n_args == 0) { + // get pin + return MP_OBJ_NEW_SMALL_INT(gpio_get(self->id)); + } else { + // set pin + bool value = mp_obj_is_true(args[0]); + if (GPIO_IS_OPEN_DRAIN(self->id)) { + MP_STATIC_ASSERT(GPIO_IN == 0 && GPIO_OUT == 1); + gpio_set_dir(self->id, 1 - value); + } else { + gpio_put(self->id, value); + } + return mp_const_none; + } +} + +// pin.init(mode, pull) +STATIC mp_obj_t machine_pin_obj_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + return machine_pin_obj_init_helper(args[0], n_args - 1, args + 1, kw_args); +} +MP_DEFINE_CONST_FUN_OBJ_KW(machine_pin_init_obj, 1, machine_pin_obj_init); + +// pin.value([value]) +STATIC mp_obj_t machine_pin_value(size_t n_args, const mp_obj_t *args) { + return machine_pin_call(args[0], n_args - 1, 0, args + 1); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_pin_value_obj, 1, 2, machine_pin_value); + +// pin.low() +STATIC mp_obj_t machine_pin_low(mp_obj_t self_in) { + machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (GPIO_IS_OPEN_DRAIN(self->id)) { + gpio_set_dir(self->id, GPIO_OUT); + } else { + gpio_clr_mask(1u << self->id); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_low_obj, machine_pin_low); + +// pin.high() +STATIC mp_obj_t machine_pin_high(mp_obj_t self_in) { + machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (GPIO_IS_OPEN_DRAIN(self->id)) { + gpio_set_dir(self->id, GPIO_IN); + } else { + gpio_set_mask(1u << self->id); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_high_obj, machine_pin_high); + +// pin.toggle() +STATIC mp_obj_t machine_pin_toggle(mp_obj_t self_in) { + machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (GPIO_IS_OPEN_DRAIN(self->id)) { + if (GPIO_IS_OUT(self->id)) { + gpio_set_dir(self->id, GPIO_IN); + } else { + gpio_set_dir(self->id, GPIO_OUT); + } + } else { + gpio_xor_mask(1u << self->id); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_toggle_obj, machine_pin_toggle); + +// pin.irq(handler=None, trigger=IRQ_FALLING|IRQ_RISING, hard=False) +STATIC mp_obj_t machine_pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_handler, ARG_trigger, ARG_hard }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_handler, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_trigger, MP_ARG_INT, {.u_int = GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE} }, + { MP_QSTR_hard, MP_ARG_BOOL, {.u_bool = false} }, + }; + machine_pin_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Get the IRQ object. + machine_pin_irq_obj_t *irq = MP_STATE_PORT(machine_pin_irq_obj[self->id]); + + // Allocate the IRQ object if it doesn't already exist. + if (irq == NULL) { + irq = m_new_obj(machine_pin_irq_obj_t); + irq->base.base.type = &mp_irq_type; + irq->base.methods = (mp_irq_methods_t *)&machine_pin_irq_methods; + irq->base.parent = MP_OBJ_FROM_PTR(self); + irq->base.handler = mp_const_none; + irq->base.ishard = false; + MP_STATE_PORT(machine_pin_irq_obj[self->id]) = irq; + } + + if (n_args > 1 || kw_args->used != 0) { + // Configure IRQ. + + // Disable all IRQs while data is updated. + gpio_set_irq_enabled(self->id, GPIO_IRQ_ALL, false); + + // Update IRQ data. + irq->base.handler = args[ARG_handler].u_obj; + irq->base.ishard = args[ARG_hard].u_bool; + irq->flags = 0; + irq->trigger = args[ARG_trigger].u_int; + + // Enable IRQ if a handler is given. + if (args[ARG_handler].u_obj != mp_const_none) { + gpio_set_irq_enabled(self->id, args[ARG_trigger].u_int, true); + } + } + + return MP_OBJ_FROM_PTR(irq); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_pin_irq_obj, 1, machine_pin_irq); + +STATIC const mp_rom_map_elem_t machine_pin_locals_dict_table[] = { + // instance methods + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_pin_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&machine_pin_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_low), MP_ROM_PTR(&machine_pin_low_obj) }, + { MP_ROM_QSTR(MP_QSTR_high), MP_ROM_PTR(&machine_pin_high_obj) }, + { MP_ROM_QSTR(MP_QSTR_off), MP_ROM_PTR(&machine_pin_low_obj) }, + { MP_ROM_QSTR(MP_QSTR_on), MP_ROM_PTR(&machine_pin_high_obj) }, + { MP_ROM_QSTR(MP_QSTR_toggle), MP_ROM_PTR(&machine_pin_toggle_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_pin_irq_obj) }, + + // class constants + { MP_ROM_QSTR(MP_QSTR_IN), MP_ROM_INT(GPIO_MODE_IN) }, + { MP_ROM_QSTR(MP_QSTR_OUT), MP_ROM_INT(GPIO_MODE_OUT) }, + { MP_ROM_QSTR(MP_QSTR_OPEN_DRAIN), MP_ROM_INT(GPIO_MODE_OPEN_DRAIN) }, + { MP_ROM_QSTR(MP_QSTR_ALT), MP_ROM_INT(GPIO_MODE_ALT) }, + { MP_ROM_QSTR(MP_QSTR_PULL_UP), MP_ROM_INT(GPIO_PULL_UP) }, + { MP_ROM_QSTR(MP_QSTR_PULL_DOWN), MP_ROM_INT(GPIO_PULL_DOWN) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_RISING), MP_ROM_INT(GPIO_IRQ_EDGE_RISE) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_FALLING), MP_ROM_INT(GPIO_IRQ_EDGE_FALL) }, +}; +STATIC MP_DEFINE_CONST_DICT(machine_pin_locals_dict, machine_pin_locals_dict_table); + +STATIC mp_uint_t pin_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { + (void)errcode; + machine_pin_obj_t *self = self_in; + + switch (request) { + case MP_PIN_READ: { + return gpio_get(self->id); + } + case MP_PIN_WRITE: { + gpio_put(self->id, arg); + return 0; + } + } + return -1; +} + +STATIC const mp_pin_p_t pin_pin_p = { + .ioctl = pin_ioctl, +}; + +const mp_obj_type_t machine_pin_type = { + { &mp_type_type }, + .name = MP_QSTR_Pin, + .print = machine_pin_print, + .make_new = mp_pin_make_new, + .call = machine_pin_call, + .protocol = &pin_pin_p, + .locals_dict = (mp_obj_t)&machine_pin_locals_dict, +}; + +STATIC mp_uint_t machine_pin_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { + machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); + machine_pin_irq_obj_t *irq = MP_STATE_PORT(machine_pin_irq_obj[self->id]); + gpio_set_irq_enabled(self->id, GPIO_IRQ_ALL, false); + irq->flags = 0; + irq->trigger = new_trigger; + gpio_set_irq_enabled(self->id, new_trigger, true); + return 0; +} + +STATIC mp_uint_t machine_pin_irq_info(mp_obj_t self_in, mp_uint_t info_type) { + machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); + machine_pin_irq_obj_t *irq = MP_STATE_PORT(machine_pin_irq_obj[self->id]); + if (info_type == MP_IRQ_INFO_FLAGS) { + return irq->flags; + } else if (info_type == MP_IRQ_INFO_TRIGGERS) { + return irq->trigger; + } + return 0; +} + +STATIC const mp_irq_methods_t machine_pin_irq_methods = { + .trigger = machine_pin_irq_trigger, + .info = machine_pin_irq_info, +}; + +mp_hal_pin_obj_t mp_hal_get_pin_obj(mp_obj_t obj) { + if (!mp_obj_is_type(obj, &machine_pin_type)) { + mp_raise_ValueError(MP_ERROR_TEXT("expecting a Pin")); + } + machine_pin_obj_t *pin = MP_OBJ_TO_PTR(obj); + return pin->id; +} diff --git a/ports/rp2/machine_pwm.c b/ports/rp2/machine_pwm.c new file mode 100644 index 0000000000..ff40c5503f --- /dev/null +++ b/ports/rp2/machine_pwm.c @@ -0,0 +1,197 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 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/runtime.h" +#include "py/mphal.h" +#include "modmachine.h" + +#include "hardware/clocks.h" +#include "hardware/pwm.h" + +/******************************************************************************/ +// MicroPython bindings for machine.PWM + +const mp_obj_type_t machine_pwm_type; + +typedef struct _machine_pwm_obj_t { + mp_obj_base_t base; + uint8_t slice; + uint8_t channel; +} machine_pwm_obj_t; + +STATIC machine_pwm_obj_t machine_pwm_obj[] = { + {{&machine_pwm_type}, 0, PWM_CHAN_A}, + {{&machine_pwm_type}, 0, PWM_CHAN_B}, + {{&machine_pwm_type}, 1, PWM_CHAN_A}, + {{&machine_pwm_type}, 1, PWM_CHAN_B}, + {{&machine_pwm_type}, 2, PWM_CHAN_A}, + {{&machine_pwm_type}, 2, PWM_CHAN_B}, + {{&machine_pwm_type}, 3, PWM_CHAN_A}, + {{&machine_pwm_type}, 3, PWM_CHAN_B}, + {{&machine_pwm_type}, 4, PWM_CHAN_A}, + {{&machine_pwm_type}, 4, PWM_CHAN_B}, + {{&machine_pwm_type}, 5, PWM_CHAN_A}, + {{&machine_pwm_type}, 5, PWM_CHAN_B}, + {{&machine_pwm_type}, 6, PWM_CHAN_A}, + {{&machine_pwm_type}, 6, PWM_CHAN_B}, + {{&machine_pwm_type}, 7, PWM_CHAN_A}, + {{&machine_pwm_type}, 7, PWM_CHAN_B}, +}; + +STATIC void machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", self->slice, self->channel); +} + +// PWM(pin) +STATIC mp_obj_t machine_pwm_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + // Check number of arguments + mp_arg_check_num(n_args, n_kw, 1, 1, false); + + // Get GPIO to connect to PWM. + uint32_t gpio = mp_hal_get_pin_obj(all_args[0]); + + // Get static peripheral object. + uint slice = pwm_gpio_to_slice_num(gpio); + uint8_t channel = pwm_gpio_to_channel(gpio); + const machine_pwm_obj_t *self = &machine_pwm_obj[slice * 2 + channel]; + + // Select PWM function for given GPIO. + gpio_set_function(gpio, GPIO_FUNC_PWM); + + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t machine_pwm_deinit(mp_obj_t self_in) { + machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in); + pwm_set_enabled(self->slice, false); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pwm_deinit_obj, machine_pwm_deinit); + +// PWM.freq([value]) +STATIC mp_obj_t machine_pwm_freq(size_t n_args, const mp_obj_t *args) { + machine_pwm_obj_t *self = MP_OBJ_TO_PTR(args[0]); + uint32_t source_hz = clock_get_hz(clk_sys); + if (n_args == 1) { + // Get frequency. + uint32_t div16 = pwm_hw->slice[self->slice].div; + uint32_t top = pwm_hw->slice[self->slice].top; + uint32_t pwm_freq = 16 * source_hz / div16 / top; + return MP_OBJ_NEW_SMALL_INT(pwm_freq); + } else { + // Set the frequency, making "top" as large as possible for maximum resolution. + // Maximum "top" is set at 65534 to be able to achieve 100% duty with 65535. + #define TOP_MAX 65534 + mp_int_t freq = mp_obj_get_int(args[1]); + uint32_t div16_top = 16 * source_hz / freq; + uint32_t top = 1; + for (;;) { + // Try a few small prime factors to get close to the desired frequency. + if (div16_top >= 16 * 5 && div16_top % 5 == 0 && top * 5 <= TOP_MAX) { + div16_top /= 5; + top *= 5; + } else if (div16_top >= 16 * 3 && div16_top % 3 == 0 && top * 3 <= TOP_MAX) { + div16_top /= 3; + top *= 3; + } else if (div16_top >= 16 * 2 && top * 2 <= TOP_MAX) { + div16_top /= 2; + top *= 2; + } else { + break; + } + } + if (div16_top < 16) { + mp_raise_ValueError(MP_ERROR_TEXT("freq too large")); + } else if (div16_top >= 256 * 16) { + mp_raise_ValueError(MP_ERROR_TEXT("freq too small")); + } + pwm_hw->slice[self->slice].div = div16_top; + pwm_hw->slice[self->slice].top = top; + return mp_const_none; + } +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_pwm_freq_obj, 1, 2, machine_pwm_freq); + +// PWM.duty_u16([value]) +STATIC mp_obj_t machine_pwm_duty_u16(size_t n_args, const mp_obj_t *args) { + machine_pwm_obj_t *self = MP_OBJ_TO_PTR(args[0]); + uint32_t top = pwm_hw->slice[self->slice].top; + if (n_args == 1) { + // Get duty cycle. + uint32_t cc = pwm_hw->slice[self->slice].cc; + cc = (cc >> (self->channel ? PWM_CH0_CC_B_LSB : PWM_CH0_CC_A_LSB)) & 0xffff; + return MP_OBJ_NEW_SMALL_INT(cc * 65535 / (top + 1)); + } else { + // Set duty cycle. + mp_int_t duty_u16 = mp_obj_get_int(args[1]); + uint32_t cc = duty_u16 * (top + 1) / 65535; + pwm_set_chan_level(self->slice, self->channel, cc); + pwm_set_enabled(self->slice, true); + return mp_const_none; + } +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_pwm_duty_u16_obj, 1, 2, machine_pwm_duty_u16); + +// PWM.duty_ns([value]) +STATIC mp_obj_t machine_pwm_duty_ns(size_t n_args, const mp_obj_t *args) { + machine_pwm_obj_t *self = MP_OBJ_TO_PTR(args[0]); + uint32_t source_hz = clock_get_hz(clk_sys); + uint32_t slice_hz = 16 * source_hz / pwm_hw->slice[self->slice].div; + if (n_args == 1) { + // Get duty cycle. + uint32_t cc = pwm_hw->slice[self->slice].cc; + cc = (cc >> (self->channel ? PWM_CH0_CC_B_LSB : PWM_CH0_CC_A_LSB)) & 0xffff; + return MP_OBJ_NEW_SMALL_INT((uint64_t)cc * 1000000000ULL / slice_hz); + } else { + // Set duty cycle. + mp_int_t duty_ns = mp_obj_get_int(args[1]); + uint32_t cc = (uint64_t)duty_ns * slice_hz / 1000000000ULL; + if (cc > 65535) { + mp_raise_ValueError(MP_ERROR_TEXT("duty larger than period")); + } + pwm_set_chan_level(self->slice, self->channel, cc); + pwm_set_enabled(self->slice, true); + return mp_const_none; + } +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_pwm_duty_ns_obj, 1, 2, machine_pwm_duty_ns); + +STATIC const mp_rom_map_elem_t machine_pwm_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_pwm_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_freq), MP_ROM_PTR(&machine_pwm_freq_obj) }, + { MP_ROM_QSTR(MP_QSTR_duty_u16), MP_ROM_PTR(&machine_pwm_duty_u16_obj) }, + { MP_ROM_QSTR(MP_QSTR_duty_ns), MP_ROM_PTR(&machine_pwm_duty_ns_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(machine_pwm_locals_dict, machine_pwm_locals_dict_table); + +const mp_obj_type_t machine_pwm_type = { + { &mp_type_type }, + .name = MP_QSTR_PWM, + .print = machine_pwm_print, + .make_new = machine_pwm_make_new, + .locals_dict = (mp_obj_dict_t *)&machine_pwm_locals_dict, +}; diff --git a/ports/rp2/machine_spi.c b/ports/rp2/machine_spi.c new file mode 100644 index 0000000000..478c061455 --- /dev/null +++ b/ports/rp2/machine_spi.c @@ -0,0 +1,278 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 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/runtime.h" +#include "py/mphal.h" +#include "py/mperrno.h" +#include "extmod/machine_spi.h" +#include "modmachine.h" + +#include "hardware/spi.h" +#include "hardware/dma.h" + +#define DEFAULT_SPI_BAUDRATE (1000000) +#define DEFAULT_SPI_POLARITY (0) +#define DEFAULT_SPI_PHASE (0) +#define DEFAULT_SPI_BITS (8) +#define DEFAULT_SPI_FIRSTBIT (SPI_MSB_FIRST) +#define DEFAULT_SPI0_SCK (6) +#define DEFAULT_SPI0_MOSI (7) +#define DEFAULT_SPI0_MISO (4) +#define DEFAULT_SPI1_SCK (10) +#define DEFAULT_SPI1_MOSI (11) +#define DEFAULT_SPI1_MISO (8) + +#define IS_VALID_PERIPH(spi, pin) ((((pin) & 8) >> 3) == (spi)) +#define IS_VALID_SCK(spi, pin) (((pin) & 3) == 2 && IS_VALID_PERIPH(spi, pin)) +#define IS_VALID_MOSI(spi, pin) (((pin) & 3) == 3 && IS_VALID_PERIPH(spi, pin)) +#define IS_VALID_MISO(spi, pin) (((pin) & 3) == 0 && IS_VALID_PERIPH(spi, pin)) + +typedef struct _machine_spi_obj_t { + mp_obj_base_t base; + spi_inst_t *const spi_inst; + uint8_t spi_id; + uint8_t polarity; + uint8_t phase; + uint8_t bits; + uint8_t firstbit; + uint8_t sck; + uint8_t mosi; + uint8_t miso; + uint32_t baudrate; +} machine_spi_obj_t; + +STATIC machine_spi_obj_t machine_spi_obj[] = { + { + {&machine_spi_type}, spi0, 0, + DEFAULT_SPI_POLARITY, DEFAULT_SPI_PHASE, DEFAULT_SPI_BITS, DEFAULT_SPI_FIRSTBIT, + DEFAULT_SPI0_SCK, DEFAULT_SPI0_MOSI, DEFAULT_SPI0_MISO, + 0, + }, + { + {&machine_spi_type}, spi1, 1, + DEFAULT_SPI_POLARITY, DEFAULT_SPI_PHASE, DEFAULT_SPI_BITS, DEFAULT_SPI_FIRSTBIT, + DEFAULT_SPI1_SCK, DEFAULT_SPI1_MOSI, DEFAULT_SPI1_MISO, + 0, + }, +}; + +STATIC void machine_spi_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_spi_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "SPI(%u, baudrate=%u, polarity=%u, phase=%u, bits=%u, sck=%u, mosi=%u, miso=%u)", + self->spi_id, self->baudrate, self->polarity, self->phase, self->bits, + self->sck, self->mosi, self->miso); +} + +mp_obj_t machine_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_id, ARG_baudrate, ARG_polarity, ARG_phase, ARG_bits, ARG_firstbit, ARG_sck, ARG_mosi, ARG_miso }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = DEFAULT_SPI_BAUDRATE} }, + { MP_QSTR_polarity, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_SPI_POLARITY} }, + { MP_QSTR_phase, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_SPI_PHASE} }, + { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_SPI_BITS} }, + { MP_QSTR_firstbit, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_SPI_FIRSTBIT} }, + { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_mosi, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_miso, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + }; + + // Parse the arguments. + 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); + + // Get the SPI bus id. + int spi_id = mp_obj_get_int(args[ARG_id].u_obj); + if (spi_id < 0 || spi_id >= MP_ARRAY_SIZE(machine_spi_obj)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("SPI(%d) doesn't exist"), spi_id); + } + + // Get static peripheral object. + machine_spi_obj_t *self = (machine_spi_obj_t *)&machine_spi_obj[spi_id]; + + // Set SCK/MOSI/MISO pins if configured. + if (args[ARG_sck].u_obj != mp_const_none) { + int sck = mp_hal_get_pin_obj(args[ARG_sck].u_obj); + if (!IS_VALID_SCK(self->spi_id, sck)) { + mp_raise_ValueError(MP_ERROR_TEXT("bad SCK pin")); + } + self->sck = sck; + } + if (args[ARG_mosi].u_obj != mp_const_none) { + int mosi = mp_hal_get_pin_obj(args[ARG_mosi].u_obj); + if (!IS_VALID_MOSI(self->spi_id, mosi)) { + mp_raise_ValueError(MP_ERROR_TEXT("bad MOSI pin")); + } + self->mosi = mosi; + } + if (args[ARG_miso].u_obj != mp_const_none) { + int miso = mp_hal_get_pin_obj(args[ARG_miso].u_obj); + if (!IS_VALID_MISO(self->spi_id, miso)) { + mp_raise_ValueError(MP_ERROR_TEXT("bad MISO pin")); + } + self->miso = miso; + } + + // Initialise the SPI peripheral if any arguments given, or it was not initialised previously. + if (n_args > 1 || n_kw > 0 || self->baudrate == 0) { + self->baudrate = args[ARG_baudrate].u_int; + self->polarity = args[ARG_polarity].u_int; + self->phase = args[ARG_phase].u_int; + self->bits = args[ARG_bits].u_int; + self->firstbit = args[ARG_firstbit].u_int; + if (self->firstbit == SPI_LSB_FIRST) { + mp_raise_NotImplementedError(MP_ERROR_TEXT("LSB")); + } + + spi_init(self->spi_inst, self->baudrate); + self->baudrate = spi_set_baudrate(self->spi_inst, self->baudrate); + spi_set_format(self->spi_inst, self->bits, self->polarity, self->phase, self->firstbit); + gpio_set_function(self->sck, GPIO_FUNC_SPI); + gpio_set_function(self->miso, GPIO_FUNC_SPI); + gpio_set_function(self->mosi, GPIO_FUNC_SPI); + } + + return MP_OBJ_FROM_PTR(self); +} + +STATIC void machine_spi_init(mp_obj_base_t *self_in, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_baudrate, ARG_polarity, ARG_phase, ARG_bits, ARG_firstbit }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_baudrate, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_polarity, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_phase, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_firstbit, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + }; + + // Parse the arguments. + machine_spi_obj_t *self = (machine_spi_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); + + // Reconfigure the baudrate if requested. + if (args[ARG_baudrate].u_int != -1) { + self->baudrate = spi_set_baudrate(self->spi_inst, args[ARG_baudrate].u_int); + } + + // Reconfigure the format if requested. + bool set_format = false; + if (args[ARG_polarity].u_int != -1) { + self->polarity = args[ARG_polarity].u_int; + set_format = true; + } + if (args[ARG_phase].u_int != -1) { + self->phase = args[ARG_phase].u_int; + set_format = true; + } + if (args[ARG_bits].u_int != -1) { + self->bits = args[ARG_bits].u_int; + set_format = true; + } + if (args[ARG_firstbit].u_int != -1) { + self->firstbit = args[ARG_firstbit].u_int; + if (self->firstbit == SPI_LSB_FIRST) { + mp_raise_NotImplementedError(MP_ERROR_TEXT("LSB")); + } + } + if (set_format) { + spi_set_format(self->spi_inst, self->bits, self->polarity, self->phase, self->firstbit); + } +} + +STATIC void machine_spi_transfer(mp_obj_base_t *self_in, size_t len, const uint8_t *src, uint8_t *dest) { + machine_spi_obj_t *self = (machine_spi_obj_t *)self_in; + // Use DMA for large transfers if channels are available + const size_t dma_min_size_threshold = 32; + int chan_tx = -1; + int chan_rx = -1; + if (len >= dma_min_size_threshold) { + // Use two DMA channels to service the two FIFOs + chan_tx = dma_claim_unused_channel(false); + chan_rx = dma_claim_unused_channel(false); + } + bool use_dma = chan_rx >= 0 && chan_tx >= 0; + // note src is guaranteed to be non-NULL + bool write_only = dest == NULL; + + if (use_dma) { + uint8_t dev_null; + dma_channel_config c = dma_channel_get_default_config(chan_tx); + channel_config_set_transfer_data_size(&c, DMA_SIZE_8); + channel_config_set_dreq(&c, spi_get_index(self->spi_inst) ? DREQ_SPI1_TX : DREQ_SPI0_TX); + dma_channel_configure(chan_tx, &c, + &spi_get_hw(self->spi_inst)->dr, + src, + len, + false); + + c = dma_channel_get_default_config(chan_rx); + channel_config_set_transfer_data_size(&c, DMA_SIZE_8); + channel_config_set_dreq(&c, spi_get_index(self->spi_inst) ? DREQ_SPI1_RX : DREQ_SPI0_RX); + channel_config_set_read_increment(&c, false); + channel_config_set_write_increment(&c, !write_only); + dma_channel_configure(chan_rx, &c, + write_only ? &dev_null : dest, + &spi_get_hw(self->spi_inst)->dr, + len, + false); + + dma_start_channel_mask((1u << chan_rx) | (1u << chan_tx)); + dma_channel_wait_for_finish_blocking(chan_rx); + dma_channel_wait_for_finish_blocking(chan_tx); + } + + // If we have claimed only one channel successfully, we should release immediately + if (chan_rx >= 0) { + dma_channel_unclaim(chan_rx); + } + if (chan_tx >= 0) { + dma_channel_unclaim(chan_tx); + } + + if (!use_dma) { + // Use software for small transfers, or if couldn't claim two DMA channels + if (write_only) { + spi_write_blocking(self->spi_inst, src, len); + } else { + spi_write_read_blocking(self->spi_inst, src, dest, len); + } + } +} + +STATIC const mp_machine_spi_p_t machine_spi_p = { + .init = machine_spi_init, + .transfer = machine_spi_transfer, +}; + +const mp_obj_type_t machine_spi_type = { + { &mp_type_type }, + .name = MP_QSTR_SPI, + .print = machine_spi_print, + .make_new = machine_spi_make_new, + .protocol = &machine_spi_p, + .locals_dict = (mp_obj_dict_t *)&mp_machine_spi_locals_dict, +}; diff --git a/ports/rp2/machine_timer.c b/ports/rp2/machine_timer.c new file mode 100644 index 0000000000..e7e8f02d55 --- /dev/null +++ b/ports/rp2/machine_timer.c @@ -0,0 +1,165 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 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/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" +#include "pico/time.h" + +#define ALARM_ID_INVALID (-1) +#define TIMER_MODE_ONE_SHOT (0) +#define TIMER_MODE_PERIODIC (1) + +typedef struct _machine_timer_obj_t { + mp_obj_base_t base; + struct alarm_pool *pool; + alarm_id_t alarm_id; + uint32_t mode; + uint64_t delta_us; // for periodic mode + mp_obj_t callback; +} machine_timer_obj_t; + +const mp_obj_type_t machine_timer_type; + +STATIC int64_t alarm_callback(alarm_id_t id, void *user_data) { + machine_timer_obj_t *self = user_data; + mp_sched_schedule(self->callback, MP_OBJ_FROM_PTR(self)); + if (self->mode == TIMER_MODE_ONE_SHOT) { + return 0; + } else { + return -self->delta_us; + } +} + +STATIC void machine_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_timer_obj_t *self = MP_OBJ_TO_PTR(self_in); + qstr mode = self->mode == TIMER_MODE_ONE_SHOT ? MP_QSTR_ONE_SHOT : MP_QSTR_PERIODIC; + mp_printf(print, "Timer(mode=%q, period=%u, tick_hz=1000000)", mode, self->delta_us); +} + +STATIC mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_mode, ARG_callback, ARG_period, ARG_tick_hz, ARG_freq, }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = TIMER_MODE_PERIODIC} }, + { MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_period, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} }, + { MP_QSTR_tick_hz, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1000} }, + { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + }; + + // Parse args + 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->mode = args[ARG_mode].u_int; + if (args[ARG_freq].u_obj != mp_const_none) { + // Frequency specified in Hz + #if MICROPY_PY_BUILTINS_FLOAT + self->delta_us = (uint64_t)(MICROPY_FLOAT_CONST(1000000.0) / mp_obj_get_float(args[ARG_freq].u_obj)); + #else + self->delta_us = 1000000 / mp_obj_get_int(args[ARG_freq].u_obj); + #endif + } else { + // Period specified + self->delta_us = (uint64_t)args[ARG_period].u_int * 1000000 / args[ARG_tick_hz].u_int; + } + if (self->delta_us < 1) { + self->delta_us = 1; + } + + self->callback = args[ARG_callback].u_obj; + self->alarm_id = alarm_pool_add_alarm_in_us(self->pool, self->delta_us, alarm_callback, self, true); + if (self->alarm_id == -1) { + mp_raise_OSError(MP_ENOMEM); + } + + return mp_const_none; +} + +STATIC mp_obj_t machine_timer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + machine_timer_obj_t *self = m_new_obj_with_finaliser(machine_timer_obj_t); + self->base.type = &machine_timer_type; + self->pool = alarm_pool_get_default(); + self->alarm_id = ALARM_ID_INVALID; + + // Get timer id (only soft timer (-1) supported at the moment) + mp_int_t id = -1; + if (n_args > 0) { + id = mp_obj_get_int(args[0]); + --n_args; + ++args; + } + if (id != -1) { + mp_raise_ValueError(MP_ERROR_TEXT("Timer doesn't exist")); + } + + if (n_args > 0 || n_kw > 0) { + // Start the timer + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + machine_timer_init_helper(self, n_args, args, &kw_args); + } + + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t machine_timer_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + machine_timer_obj_t *self = MP_OBJ_TO_PTR(args[0]); + if (self->alarm_id != ALARM_ID_INVALID) { + alarm_pool_cancel_alarm(self->pool, self->alarm_id); + self->alarm_id = ALARM_ID_INVALID; + } + return machine_timer_init_helper(self, n_args - 1, args + 1, kw_args); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_timer_init_obj, 1, machine_timer_init); + +STATIC mp_obj_t machine_timer_deinit(mp_obj_t self_in) { + machine_timer_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->alarm_id != ALARM_ID_INVALID) { + alarm_pool_cancel_alarm(self->pool, self->alarm_id); + self->alarm_id = ALARM_ID_INVALID; + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_timer_deinit_obj, machine_timer_deinit); + +STATIC const mp_rom_map_elem_t machine_timer_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&machine_timer_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_timer_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_timer_deinit_obj) }, + + { MP_ROM_QSTR(MP_QSTR_ONE_SHOT), MP_ROM_INT(TIMER_MODE_ONE_SHOT) }, + { MP_ROM_QSTR(MP_QSTR_PERIODIC), MP_ROM_INT(TIMER_MODE_PERIODIC) }, +}; +STATIC MP_DEFINE_CONST_DICT(machine_timer_locals_dict, machine_timer_locals_dict_table); + +const mp_obj_type_t machine_timer_type = { + { &mp_type_type }, + .name = MP_QSTR_Timer, + .print = machine_timer_print, + .make_new = machine_timer_make_new, + .locals_dict = (mp_obj_dict_t *)&machine_timer_locals_dict, +}; diff --git a/ports/rp2/machine_uart.c b/ports/rp2/machine_uart.c new file mode 100644 index 0000000000..30446e688d --- /dev/null +++ b/ports/rp2/machine_uart.c @@ -0,0 +1,246 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 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/runtime.h" +#include "py/stream.h" +#include "py/mphal.h" +#include "py/mperrno.h" +#include "modmachine.h" + +#include "hardware/uart.h" + +#define DEFAULT_UART_BAUDRATE (115200) +#define DEFAULT_UART_BITS (8) +#define DEFAULT_UART_STOP (1) +#define DEFAULT_UART0_TX (0) +#define DEFAULT_UART0_RX (1) +#define DEFAULT_UART1_TX (4) +#define DEFAULT_UART1_RX (5) + +#define IS_VALID_PERIPH(uart, pin) (((((pin) + 4) & 8) >> 3) == (uart)) +#define IS_VALID_TX(uart, pin) (((pin) & 3) == 0 && IS_VALID_PERIPH(uart, pin)) +#define IS_VALID_RX(uart, pin) (((pin) & 3) == 1 && IS_VALID_PERIPH(uart, pin)) + +typedef struct _machine_uart_obj_t { + mp_obj_base_t base; + uart_inst_t *const uart; + uint8_t uart_id; + uint32_t baudrate; + uint8_t bits; + uart_parity_t parity; + uint8_t stop; + uint8_t tx; + uint8_t rx; +} machine_uart_obj_t; + +STATIC machine_uart_obj_t machine_uart_obj[] = { + {{&machine_uart_type}, uart0, 0, 0, DEFAULT_UART_BITS, UART_PARITY_NONE, DEFAULT_UART_STOP, DEFAULT_UART0_TX, DEFAULT_UART0_RX}, + {{&machine_uart_type}, uart1, 1, 0, DEFAULT_UART_BITS, UART_PARITY_NONE, DEFAULT_UART_STOP, DEFAULT_UART1_TX, DEFAULT_UART1_RX}, +}; + +STATIC const char *_parity_name[] = {"None", "0", "1"}; + +/******************************************************************************/ +// MicroPython bindings for UART + +STATIC void machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, tx=%d, rx=%d)", + self->uart_id, self->baudrate, self->bits, _parity_name[self->parity], + self->stop, self->tx, self->rx); +} + +STATIC mp_obj_t machine_uart_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_id, ARG_baudrate, ARG_bits, ARG_parity, ARG_stop, ARG_tx, ARG_rx }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_bits, MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_parity, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_INT(-1)} }, + { MP_QSTR_stop, MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_tx, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_rx, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Get UART bus. + int uart_id = mp_obj_get_int(args[ARG_id].u_obj); + if (uart_id < 0 || uart_id >= MP_ARRAY_SIZE(machine_uart_obj)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("UART(%d) doesn't exist"), uart_id); + } + + // Get static peripheral object. + machine_uart_obj_t *self = (machine_uart_obj_t *)&machine_uart_obj[uart_id]; + + // Set baudrate if configured. + if (args[ARG_baudrate].u_int > 0) { + self->baudrate = args[ARG_baudrate].u_int; + } + + // Set bits if configured. + if (args[ARG_bits].u_int > 0) { + self->bits = args[ARG_bits].u_int; + } + + // Set parity if configured. + if (args[ARG_parity].u_obj != MP_OBJ_NEW_SMALL_INT(-1)) { + if (args[ARG_parity].u_obj == mp_const_none) { + self->parity = UART_PARITY_NONE; + } else if (mp_obj_get_int(args[ARG_parity].u_obj) & 1) { + self->parity = UART_PARITY_ODD; + } else { + self->parity = UART_PARITY_EVEN; + } + } + + // Set stop bits if configured. + if (args[ARG_stop].u_int > 0) { + self->stop = args[ARG_stop].u_int; + } + + // Set TX/RX pins if configured. + if (args[ARG_tx].u_obj != mp_const_none) { + int tx = mp_hal_get_pin_obj(args[ARG_tx].u_obj); + if (!IS_VALID_TX(self->uart_id, tx)) { + mp_raise_ValueError(MP_ERROR_TEXT("bad TX pin")); + } + self->tx = tx; + } + if (args[ARG_rx].u_obj != mp_const_none) { + int rx = mp_hal_get_pin_obj(args[ARG_rx].u_obj); + if (!IS_VALID_RX(self->uart_id, rx)) { + mp_raise_ValueError(MP_ERROR_TEXT("bad RX pin")); + } + self->rx = rx; + } + + // Initialise the UART peripheral if any arguments given, or it was not initialised previously. + if (n_args > 1 || n_kw > 0 || self->baudrate == 0) { + if (self->baudrate == 0) { + self->baudrate = DEFAULT_UART_BAUDRATE; + } + uart_init(self->uart, self->baudrate); + uart_set_format(self->uart, self->bits, self->stop, self->parity); + uart_set_fifo_enabled(self->uart, true); + gpio_set_function(self->tx, GPIO_FUNC_UART); + gpio_set_function(self->rx, GPIO_FUNC_UART); + } + + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t machine_uart_any(mp_obj_t self_in) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_NEW_SMALL_INT(uart_is_readable(self->uart)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_uart_any_obj, machine_uart_any); + +STATIC mp_obj_t machine_uart_sendbreak(mp_obj_t self_in) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + uart_set_break(self->uart, true); + mp_hal_delay_us(13000000 / self->baudrate + 1); + uart_set_break(self->uart, false); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_uart_sendbreak_obj, machine_uart_sendbreak); + +STATIC const mp_rom_map_elem_t machine_uart_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&machine_uart_any_obj) }, + + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, + { 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) }, +}; +STATIC MP_DEFINE_CONST_DICT(machine_uart_locals_dict, machine_uart_locals_dict_table); + +STATIC mp_uint_t machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + // TODO support timeout + uint8_t *dest = buf_in; + for (size_t i = 0; i < size; ++i) { + while (!uart_is_readable(self->uart)) { + MICROPY_EVENT_POLL_HOOK + } + *dest++ = uart_get_hw(self->uart)->dr; + } + return size; +} + +STATIC mp_uint_t machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + // TODO support timeout + const uint8_t *src = buf_in; + for (size_t i = 0; i < size; ++i) { + while (!uart_is_writable(self->uart)) { + MICROPY_EVENT_POLL_HOOK + } + uart_get_hw(self->uart)->dr = *src++; + } + return size; +} + +STATIC mp_uint_t machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, mp_uint_t arg, int *errcode) { + machine_uart_obj_t *self = self_in; + mp_uint_t ret; + if (request == MP_STREAM_POLL) { + uintptr_t flags = arg; + ret = 0; + if ((flags & MP_STREAM_POLL_RD) && uart_is_readable(self->uart)) { + ret |= MP_STREAM_POLL_RD; + } + if ((flags & MP_STREAM_POLL_WR) && uart_is_writable(self->uart)) { + ret |= MP_STREAM_POLL_WR; + } + } else { + *errcode = MP_EINVAL; + ret = MP_STREAM_ERROR; + } + return ret; +} + +STATIC const mp_stream_p_t uart_stream_p = { + .read = machine_uart_read, + .write = machine_uart_write, + .ioctl = machine_uart_ioctl, + .is_text = false, +}; + +const mp_obj_type_t machine_uart_type = { + { &mp_type_type }, + .name = MP_QSTR_UART, + .print = machine_uart_print, + .make_new = machine_uart_make_new, + .getiter = mp_identity_getiter, + .iternext = mp_stream_unbuffered_iter, + .protocol = &uart_stream_p, + .locals_dict = (mp_obj_dict_t *)&machine_uart_locals_dict, +}; diff --git a/ports/rp2/machine_wdt.c b/ports/rp2/machine_wdt.c new file mode 100644 index 0000000000..38e0597018 --- /dev/null +++ b/ports/rp2/machine_wdt.c @@ -0,0 +1,78 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 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/runtime.h" +#include "modmachine.h" + +#include "hardware/watchdog.h" + +typedef struct _machine_wdt_obj_t { + mp_obj_base_t base; +} machine_wdt_obj_t; + +STATIC const machine_wdt_obj_t machine_wdt = {{&machine_wdt_type}}; + +STATIC mp_obj_t machine_wdt_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_id, ARG_timeout }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_timeout, MP_ARG_INT, {.u_int = 5000} }, + }; + + // Parse the arguments. + 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); + + // Verify the WDT id. + mp_int_t id = args[ARG_id].u_int; + if (id != 0) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("WDT(%d) doesn't exist"), id); + } + + // Start the watchdog (timeout is in milliseconds). + watchdog_enable(args[ARG_timeout].u_int, false); + + return MP_OBJ_FROM_PTR(&machine_wdt); +} + +STATIC mp_obj_t machine_wdt_feed(mp_obj_t self_in) { + (void)self_in; + watchdog_update(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_wdt_feed_obj, machine_wdt_feed); + +STATIC const mp_rom_map_elem_t machine_wdt_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_feed), MP_ROM_PTR(&machine_wdt_feed_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(machine_wdt_locals_dict, machine_wdt_locals_dict_table); + +const mp_obj_type_t machine_wdt_type = { + { &mp_type_type }, + .name = MP_QSTR_WDT, + .make_new = machine_wdt_make_new, + .locals_dict = (mp_obj_dict_t *)&machine_wdt_locals_dict, +}; diff --git a/ports/rp2/main.c b/ports/rp2/main.c new file mode 100644 index 0000000000..3e956ae78c --- /dev/null +++ b/ports/rp2/main.c @@ -0,0 +1,208 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/compile.h" +#include "py/runtime.h" +#include "py/gc.h" +#include "py/mperrno.h" +#include "py/stackctrl.h" +#include "lib/mp-readline/readline.h" +#include "lib/utils/gchelper.h" +#include "lib/utils/pyexec.h" +#include "tusb.h" +#include "uart.h" +#include "modmachine.h" +#include "modrp2.h" +#include "genhdr/mpversion.h" + +#include "pico/stdlib.h" +#include "pico/binary_info.h" +#include "hardware/rtc.h" +#include "hardware/structs/rosc.h" + +extern uint8_t __StackTop, __StackBottom; +static char gc_heap[192 * 1024]; + +// Embed version info in the binary in machine readable form +bi_decl(bi_program_version_string(MICROPY_GIT_TAG)); + +// Add a section to the picotool output similar to program features, but for frozen modules +// (it will aggregate BINARY_INFO_ID_MP_FROZEN binary info) +bi_decl(bi_program_feature_group_with_flags(BINARY_INFO_TAG_MICROPYTHON, + BINARY_INFO_ID_MP_FROZEN, "frozen modules", + BI_NAMED_GROUP_SEPARATE_COMMAS | BI_NAMED_GROUP_SORT_ALPHA)); + +int main(int argc, char **argv) { + #if MICROPY_HW_ENABLE_UART_REPL + bi_decl(bi_program_feature("UART REPL")) + setup_default_uart(); + mp_uart_init(); + #endif + + #if MICROPY_HW_ENABLE_USBDEV + bi_decl(bi_program_feature("USB REPL")) + tusb_init(); + #endif + + #if MICROPY_PY_THREAD + bi_decl(bi_program_feature("thread support")) + mp_thread_init(); + #endif + + // Start and initialise the RTC + datetime_t t = { + .year = 2021, + .month = 1, + .day = 1, + .dotw = 5, // 0 is Sunday, so 5 is Friday + .hour = 0, + .min = 0, + .sec = 0, + }; + rtc_init(); + rtc_set_datetime(&t); + + // Initialise stack extents and GC heap. + mp_stack_set_top(&__StackTop); + mp_stack_set_limit(&__StackTop - &__StackBottom - 256); + gc_init(&gc_heap[0], &gc_heap[MP_ARRAY_SIZE(gc_heap)]); + + for (;;) { + + // Initialise MicroPython runtime. + mp_init(); + mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_path), 0); + mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR_)); + mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__slash_lib)); + mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_argv), 0); + + // Initialise sub-systems. + readline_init0(); + machine_pin_init(); + rp2_pio_init(); + + // Execute _boot.py to set up the filesystem. + pyexec_frozen_module("_boot.py"); + + // Execute user scripts. + pyexec_file_if_exists("boot.py"); + if (pyexec_mode_kind == PYEXEC_MODE_FRIENDLY_REPL) { + pyexec_file_if_exists("main.py"); + } + + for (;;) { + if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) { + if (pyexec_raw_repl() != 0) { + break; + } + } else { + if (pyexec_friendly_repl() != 0) { + break; + } + } + } + + mp_printf(MP_PYTHON_PRINTER, "MPY: soft reboot\n"); + rp2_pio_deinit(); + machine_pin_deinit(); + gc_sweep_all(); + mp_deinit(); + } + + return 0; +} + +void gc_collect(void) { + gc_collect_start(); + gc_helper_collect_regs_and_stack(); + #if MICROPY_PY_THREAD + mp_thread_gc_others(); + #endif + gc_collect_end(); +} + +void nlr_jump_fail(void *val) { + printf("FATAL: uncaught exception %p\n", val); + mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(val)); + for (;;) { + __breakpoint(); + } +} + +#ifndef NDEBUG +void MP_WEAK __assert_func(const char *file, int line, const char *func, const char *expr) { + printf("Assertion '%s' failed, at file %s:%d\n", expr, file, line); + panic("Assertion failed"); +} +#endif + +uint32_t rosc_random_u32(void) { + uint32_t value = 0; + for (size_t i = 0; i < 32; ++i) { + value = value << 1 | rosc_hw->randombit; + } + return value; +} + +const char rp2_help_text[] = + "Welcome to MicroPython!\n" + "\n" + "For online help please visit https://micropython.org/help/.\n" + "\n" + "For access to the hardware use the 'machine' module. RP2 specific commands\n" + "are in the 'rp2' module.\n" + "\n" + "Quick overview of some objects:\n" + " machine.Pin(pin) -- get a pin, eg machine.Pin(0)\n" + " machine.Pin(pin, m, [p]) -- get a pin and configure it for IO mode m, pull mode p\n" + " methods: init(..), value([v]), high(), low(), irq(handler)\n" + " machine.ADC(pin) -- make an analog object from a pin\n" + " methods: read_u16()\n" + " machine.PWM(pin) -- make a PWM object from a pin\n" + " methods: deinit(), freq([f]), duty_u16([d]), duty_ns([d])\n" + " machine.I2C(id) -- create an I2C object (id=0,1)\n" + " methods: readfrom(addr, buf, stop=True), writeto(addr, buf, stop=True)\n" + " readfrom_mem(addr, memaddr, arg), writeto_mem(addr, memaddr, arg)\n" + " machine.SPI(id, baudrate=1000000) -- create an SPI object (id=0,1)\n" + " methods: read(nbytes, write=0x00), write(buf), write_readinto(wr_buf, rd_buf)\n" + " machine.Timer(freq, callback) -- create a software timer object\n" + " eg: machine.Timer(freq=1, callback=lambda t:print(t))\n" + "\n" + "Pins are numbered 0-29, and 26-29 have ADC capabilities\n" + "Pin IO modes are: Pin.IN, Pin.OUT, Pin.ALT\n" + "Pin pull modes are: Pin.PULL_UP, Pin.PULL_DOWN\n" + "\n" + "Useful control commands:\n" + " CTRL-C -- interrupt a running program\n" + " CTRL-D -- on a blank line, do a soft reset of the board\n" + " CTRL-E -- on a blank line, enter paste mode\n" + "\n" + "For further help on a specific object, type help(obj)\n" + "For a list of available modules, type help('modules')\n" +; + diff --git a/ports/rp2/manifest.py b/ports/rp2/manifest.py new file mode 100644 index 0000000000..a56d4b1b09 --- /dev/null +++ b/ports/rp2/manifest.py @@ -0,0 +1,3 @@ +freeze("modules") +freeze("$(MPY_DIR)/drivers/onewire") +include("$(MPY_DIR)/extmod/uasyncio/manifest.py") diff --git a/ports/rp2/memmap_mp.ld b/ports/rp2/memmap_mp.ld new file mode 100644 index 0000000000..5cebd33466 --- /dev/null +++ b/ports/rp2/memmap_mp.ld @@ -0,0 +1,252 @@ +/* Based on GCC ARM embedded samples. + Defines the following symbols for use by code: + __exidx_start + __exidx_end + __etext + __data_start__ + __preinit_array_start + __preinit_array_end + __init_array_start + __init_array_end + __fini_array_start + __fini_array_end + __data_end__ + __bss_start__ + __bss_end__ + __end__ + end + __HeapLimit + __StackLimit + __StackTop + __stack (== StackTop) +*/ + +MEMORY +{ + FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 2048k + RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k + SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k + SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k +} + +ENTRY(_entry_point) + +SECTIONS +{ + /* Second stage bootloader is prepended to the image. It must be 256 bytes big + and checksummed. It is usually built by the boot_stage2 target + in the Pico SDK + */ + + .flash_begin : { + __flash_binary_start = .; + } > FLASH + + .boot2 : { + __boot2_start__ = .; + KEEP (*(.boot2)) + __boot2_end__ = .; + } > FLASH + + ASSERT(__boot2_end__ - __boot2_start__ == 256, + "ERROR: Pico second stage bootloader must be 256 bytes in size") + + /* The second stage will always enter the image at the start of .text. + The debugger will use the ELF entry point, which is the _entry_point + symbol if present, otherwise defaults to start of .text. + This can be used to transfer control back to the bootrom on debugger + launches only, to perform proper flash setup. + */ + + .text : { + __reset_start = .; + KEEP (*(.reset)) + . = ALIGN(256); + __reset_end = .; + ASSERT(__reset_end - __reset_start == 256, "ERROR: reset section should only be 256 bytes"); + KEEP (*(.vectors)) + /* TODO revisit this now memset/memcpy/float in ROM */ + /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from + * FLASH ... we will include any thing excluded here in .data below by default */ + *(.init) + /* Change for MicroPython... excluse gc.c, parse.c, vm.c from flash */ + *(EXCLUDE_FILE(*libgcc.a: *libc.a: *lib_a-mem*.o *libm.a: *gc.c.obj *vm.c.obj *parse.c.obj) .text*) + *(.fini) + /* Pull all c'tors into .text */ + *crtbegin.o(.ctors) + *crtbegin?.o(.ctors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) + *(SORT(.ctors.*)) + *(.ctors) + /* Followed by destructors */ + *crtbegin.o(.dtors) + *crtbegin?.o(.dtors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) + *(SORT(.dtors.*)) + *(.dtors) + + *(.eh_frame*) + . = ALIGN(4); + } > FLASH + + .rodata : { + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*) + . = ALIGN(4); + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*))) + . = ALIGN(4); + } > FLASH + + .ARM.extab : + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + __exidx_start = .; + .ARM.exidx : + { + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + } > FLASH + __exidx_end = .; + + /* Machine inspectable binary information */ + . = ALIGN(4); + __binary_info_start = .; + .binary_info : + { + KEEP(*(.binary_info.keep.*)) + *(.binary_info.*) + } > FLASH + __binary_info_end = .; + . = ALIGN(4); + + /* End of .text-like segments */ + __etext = .; + + .ram_vector_table (COPY): { + *(.ram_vector_table) + } > RAM + + .data : { + __data_start__ = .; + *(vtable) + + *(.time_critical*) + + /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */ + *(.text*) + . = ALIGN(4); + *(.rodata*) + . = ALIGN(4); + + *(.data*) + + . = ALIGN(4); + *(.after_data.*) + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__mutex_array_start = .); + KEEP(*(SORT(.mutex_array.*))) + KEEP(*(.mutex_array)) + PROVIDE_HIDDEN (__mutex_array_end = .); + + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP(*(SORT(.preinit_array.*))) + KEEP(*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + + . = ALIGN(4); + /* init data */ + PROVIDE_HIDDEN (__init_array_start = .); + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + + . = ALIGN(4); + /* finit data */ + PROVIDE_HIDDEN (__fini_array_start = .); + *(SORT(.fini_array.*)) + *(.fini_array) + PROVIDE_HIDDEN (__fini_array_end = .); + + *(.jcr) + . = ALIGN(4); + /* All data end */ + __data_end__ = .; + } > RAM AT> FLASH + + .uninitialized_data (COPY): { + . = ALIGN(4); + *(.uninitialized_data*) + } > RAM + + /* Start and end symbols must be word-aligned */ + .scratch_x : { + __scratch_x_start__ = .; + *(.scratch_x.*) + . = ALIGN(4); + __scratch_x_end__ = .; + } > SCRATCH_X AT > FLASH + __scratch_x_source__ = LOADADDR(.scratch_x); + + .scratch_y : { + __scratch_y_start__ = .; + *(.scratch_y.*) + . = ALIGN(4); + __scratch_y_end__ = .; + } > SCRATCH_Y AT > FLASH + __scratch_y_source__ = LOADADDR(.scratch_y); + + .bss : { + . = ALIGN(4); + __bss_start__ = .; + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) + *(COMMON) + . = ALIGN(4); + __bss_end__ = .; + } > RAM + + .heap (COPY): + { + __end__ = .; + end = __end__; + *(.heap*) + __HeapLimit = .; + } > RAM + + /* .stack*_dummy section doesn't contains any symbols. It is only + * used for linker to calculate size of stack sections, and assign + * values to stack symbols later + * + * stack1 section may be empty/missing if platform_launch_core1 is not used */ + + /* by default we put core 0 stack at the end of scratch Y, so that if core 1 + * stack is not used then all of SCRATCH_X is free. + */ + .stack1_dummy (COPY): + { + *(.stack1*) + } > SCRATCH_X + .stack_dummy (COPY): + { + *(.stack*) + } > SCRATCH_Y + + .flash_end : { + __flash_binary_end = .; + } > FLASH + + /* stack limit is poorly named, but historically is maximum heap ptr */ + __StackLimit = ORIGIN(RAM) + LENGTH(RAM); + __StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X); + __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); + __StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy); + __StackBottom = __StackTop - SIZEOF(.stack_dummy); + PROVIDE(__stack = __StackTop); + + /* Check if data + heap + stack exceeds RAM limit */ + ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed") + /* todo assert on extra code */ +} + diff --git a/ports/rp2/micropy_extmod.cmake b/ports/rp2/micropy_extmod.cmake new file mode 100644 index 0000000000..1e968bef63 --- /dev/null +++ b/ports/rp2/micropy_extmod.cmake @@ -0,0 +1,40 @@ +# CMake fragment for MicroPython extmod component + +set(SOURCE_EXTMOD + ${MPY_DIR}/extmod/machine_i2c.c + ${MPY_DIR}/extmod/machine_mem.c + ${MPY_DIR}/extmod/machine_pulse.c + ${MPY_DIR}/extmod/machine_signal.c + ${MPY_DIR}/extmod/machine_spi.c + ${MPY_DIR}/extmod/modbtree.c + ${MPY_DIR}/extmod/modframebuf.c + ${MPY_DIR}/extmod/modonewire.c + ${MPY_DIR}/extmod/moduasyncio.c + ${MPY_DIR}/extmod/modubinascii.c + ${MPY_DIR}/extmod/moducryptolib.c + ${MPY_DIR}/extmod/moductypes.c + ${MPY_DIR}/extmod/moduhashlib.c + ${MPY_DIR}/extmod/moduheapq.c + ${MPY_DIR}/extmod/modujson.c + ${MPY_DIR}/extmod/modurandom.c + ${MPY_DIR}/extmod/modure.c + ${MPY_DIR}/extmod/moduselect.c + ${MPY_DIR}/extmod/modussl_axtls.c + ${MPY_DIR}/extmod/modussl_mbedtls.c + ${MPY_DIR}/extmod/modutimeq.c + ${MPY_DIR}/extmod/moduwebsocket.c + ${MPY_DIR}/extmod/moduzlib.c + ${MPY_DIR}/extmod/modwebrepl.c + ${MPY_DIR}/extmod/uos_dupterm.c + ${MPY_DIR}/extmod/utime_mphal.c + ${MPY_DIR}/extmod/vfs.c + ${MPY_DIR}/extmod/vfs_blockdev.c + ${MPY_DIR}/extmod/vfs_fat.c + ${MPY_DIR}/extmod/vfs_fat_diskio.c + ${MPY_DIR}/extmod/vfs_fat_file.c + ${MPY_DIR}/extmod/vfs_lfs.c + ${MPY_DIR}/extmod/vfs_posix.c + ${MPY_DIR}/extmod/vfs_posix_file.c + ${MPY_DIR}/extmod/vfs_reader.c + ${MPY_DIR}/extmod/virtpin.c +) diff --git a/ports/rp2/micropy_py.cmake b/ports/rp2/micropy_py.cmake new file mode 100644 index 0000000000..aeb7e2b9b5 --- /dev/null +++ b/ports/rp2/micropy_py.cmake @@ -0,0 +1,134 @@ +# CMake fragment for MicroPython core py component + +set(MPY_PY_DIR "${MPY_DIR}/py") +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") + +# 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 +) diff --git a/ports/rp2/micropy_rules.cmake b/ports/rp2/micropy_rules.cmake new file mode 100644 index 0000000000..ade9b8c92d --- /dev/null +++ b/ports/rp2/micropy_rules.cmake @@ -0,0 +1,91 @@ +# CMake fragment for MicroPython rules + +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} ${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_PY_QSTRDEFS} ${MPY_QSTR_DEFS} ${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=${PROJECT_SOURCE_DIR}" -b "${CMAKE_BINARY_DIR}" -f${MPY_CROSS_FLAGS} ${FROZEN_MANIFEST} + DEPENDS FORCE_BUILD + ${MPY_QSTR_DEFS_GENERATED} + VERBATIM +) diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c new file mode 100644 index 0000000000..228e9543c1 --- /dev/null +++ b/ports/rp2/modmachine.c @@ -0,0 +1,101 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 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/runtime.h" +#include "py/mphal.h" +#include "extmod/machine_i2c.h" +#include "extmod/machine_mem.h" +#include "extmod/machine_spi.h" + +#include "modmachine.h" +#include "hardware/clocks.h" +#include "hardware/watchdog.h" +#include "pico/bootrom.h" + +#define RP2_RESET_PWRON (1) +#define RP2_RESET_WDT (3) + +STATIC mp_obj_t machine_reset(void) { + watchdog_reboot(0, SRAM_END, 0); + for (;;) { + __wfi(); + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_0(machine_reset_obj, machine_reset); + +STATIC mp_obj_t machine_reset_cause(void) { + int reset_cause; + if (watchdog_caused_reboot()) { + reset_cause = RP2_RESET_WDT; + } else { + reset_cause = RP2_RESET_PWRON; + } + return MP_OBJ_NEW_SMALL_INT(reset_cause); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_reset_cause_obj, machine_reset_cause); + +STATIC mp_obj_t machine_bootloader(void) { + reset_usb_boot(0, 0); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_0(machine_bootloader_obj, machine_bootloader); + +STATIC mp_obj_t machine_freq(void) { + return MP_OBJ_NEW_SMALL_INT(clock_get_hz(clk_sys)); +} +MP_DEFINE_CONST_FUN_OBJ_0(machine_freq_obj, machine_freq); + +STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_umachine) }, + { MP_ROM_QSTR(MP_QSTR_reset), MP_ROM_PTR(&machine_reset_obj) }, + { MP_ROM_QSTR(MP_QSTR_reset_cause), MP_ROM_PTR(&machine_reset_cause_obj) }, + { MP_ROM_QSTR(MP_QSTR_bootloader), MP_ROM_PTR(&machine_bootloader_obj) }, + { MP_ROM_QSTR(MP_QSTR_freq), MP_ROM_PTR(&machine_freq_obj) }, + { MP_ROM_QSTR(MP_QSTR_mem8), MP_ROM_PTR(&machine_mem8_obj) }, + { MP_ROM_QSTR(MP_QSTR_mem16), MP_ROM_PTR(&machine_mem16_obj) }, + { MP_ROM_QSTR(MP_QSTR_mem32), MP_ROM_PTR(&machine_mem32_obj) }, + + { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&machine_adc_type) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_hw_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&machine_pin_type) }, + { MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&machine_pwm_type) }, + { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_spi_type) }, + { MP_ROM_QSTR(MP_QSTR_SoftSPI), MP_ROM_PTR(&mp_machine_soft_spi_type) }, + { MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&machine_timer_type) }, + { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&machine_uart_type) }, + { MP_ROM_QSTR(MP_QSTR_WDT), MP_ROM_PTR(&machine_wdt_type) }, + + { MP_ROM_QSTR(MP_QSTR_PWRON_RESET), MP_ROM_INT(RP2_RESET_PWRON) }, + { MP_ROM_QSTR(MP_QSTR_WDT_RESET), MP_ROM_INT(RP2_RESET_WDT) }, +}; +STATIC MP_DEFINE_CONST_DICT(machine_module_globals, machine_module_globals_table); + +const mp_obj_module_t mp_module_machine = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&machine_module_globals, +}; diff --git a/ports/rp2/modmachine.h b/ports/rp2/modmachine.h new file mode 100644 index 0000000000..d09c83aee2 --- /dev/null +++ b/ports/rp2/modmachine.h @@ -0,0 +1,18 @@ +#ifndef MICROPY_INCLUDED_RP2_MODMACHINE_H +#define MICROPY_INCLUDED_RP2_MODMACHINE_H + +#include "py/obj.h" + +extern const mp_obj_type_t machine_adc_type; +extern const mp_obj_type_t machine_hw_i2c_type; +extern const mp_obj_type_t machine_pin_type; +extern const mp_obj_type_t machine_pwm_type; +extern const mp_obj_type_t machine_spi_type; +extern const mp_obj_type_t machine_timer_type; +extern const mp_obj_type_t machine_uart_type; +extern const mp_obj_type_t machine_wdt_type; + +void machine_pin_init(void); +void machine_pin_deinit(void); + +#endif // MICROPY_INCLUDED_RP2_MODMACHINE_H diff --git a/ports/rp2/modrp2.c b/ports/rp2/modrp2.c new file mode 100644 index 0000000000..8009fa33f8 --- /dev/null +++ b/ports/rp2/modrp2.c @@ -0,0 +1,41 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 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/runtime.h" +#include "modrp2.h" + +STATIC const mp_rom_map_elem_t rp2_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_rp2) }, + { MP_ROM_QSTR(MP_QSTR_Flash), MP_ROM_PTR(&rp2_flash_type) }, + { MP_ROM_QSTR(MP_QSTR_PIO), MP_ROM_PTR(&rp2_pio_type) }, + { MP_ROM_QSTR(MP_QSTR_StateMachine), MP_ROM_PTR(&rp2_state_machine_type) }, +}; +STATIC MP_DEFINE_CONST_DICT(rp2_module_globals, rp2_module_globals_table); + +const mp_obj_module_t mp_module_rp2 = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&rp2_module_globals, +}; diff --git a/ports/rp2/modrp2.h b/ports/rp2/modrp2.h new file mode 100644 index 0000000000..805c785f2d --- /dev/null +++ b/ports/rp2/modrp2.h @@ -0,0 +1,38 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 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_RP2_MODRP2_H +#define MICROPY_INCLUDED_RP2_MODRP2_H + +#include "py/obj.h" + +extern const mp_obj_type_t rp2_flash_type; +extern const mp_obj_type_t rp2_pio_type; +extern const mp_obj_type_t rp2_state_machine_type; + +void rp2_pio_init(void); +void rp2_pio_deinit(void); + +#endif // MICROPY_INCLUDED_RP2_MODRP2_H diff --git a/ports/rp2/modules/_boot.py b/ports/rp2/modules/_boot.py new file mode 100644 index 0000000000..099e5aba05 --- /dev/null +++ b/ports/rp2/modules/_boot.py @@ -0,0 +1,15 @@ +import os +import machine, rp2 + + +# Try to mount the filesystem, and format the flash if it doesn't exist. +# Note: the flash requires the programming size to be aligned to 256 bytes. +bdev = rp2.Flash() +try: + vfs = os.VfsLfs2(bdev, progsize=256) +except: + os.VfsLfs2.mkfs(bdev, progsize=256) + vfs = os.VfsLfs2(bdev, progsize=256) +os.mount(vfs, "/") + +del os, bdev, vfs diff --git a/ports/rp2/modules/rp2.py b/ports/rp2/modules/rp2.py new file mode 100644 index 0000000000..a3adcdc512 --- /dev/null +++ b/ports/rp2/modules/rp2.py @@ -0,0 +1,294 @@ +# rp2 module: uses C code from _rp2, plus asm_pio decorator implemented in Python. +# MIT license; Copyright (c) 2020-2021 Damien P. George + +from _rp2 import * +from micropython import const + +_PROG_DATA = const(0) +_PROG_OFFSET_PIO0 = const(1) +_PROG_OFFSET_PIO1 = const(2) +_PROG_EXECCTRL = const(3) +_PROG_SHIFTCTRL = const(4) +_PROG_OUT_PINS = const(5) +_PROG_SET_PINS = const(6) +_PROG_SIDESET_PINS = const(7) +_PROG_MAX_FIELDS = const(8) + + +class PIOASMError(Exception): + pass + + +class PIOASMEmit: + def __init__( + self, + *, + out_init=None, + set_init=None, + sideset_init=None, + in_shiftdir=0, + out_shiftdir=0, + autopush=False, + autopull=False, + push_thresh=32, + pull_thresh=32 + ): + from array import array + + self.labels = {} + execctrl = 0 + shiftctrl = ( + (pull_thresh & 0x1F) << 25 + | (push_thresh & 0x1F) << 20 + | out_shiftdir << 19 + | in_shiftdir << 18 + | autopull << 17 + | autopush << 16 + ) + self.prog = [array("H"), -1, -1, execctrl, shiftctrl, out_init, set_init, sideset_init] + + self.wrap_used = False + + if sideset_init is None: + self.sideset_count = 0 + elif isinstance(sideset_init, int): + self.sideset_count = 1 + else: + self.sideset_count = len(sideset_init) + + def start_pass(self, pass_): + if pass_ == 1: + if not self.wrap_used and self.num_instr: + self.wrap() + self.delay_max = 31 + if self.sideset_count: + self.sideset_opt = self.num_sideset != self.num_instr + if self.sideset_opt: + self.prog[_PROG_EXECCTRL] |= 1 << 30 + self.sideset_count += 1 + self.delay_max >>= self.sideset_count + self.pass_ = pass_ + self.num_instr = 0 + self.num_sideset = 0 + + def __getitem__(self, key): + return self.delay(key) + + def delay(self, delay): + if self.pass_ > 0: + if delay > self.delay_max: + raise PIOASMError("delay too large") + self.prog[_PROG_DATA][-1] |= delay << 8 + return self + + def side(self, value): + self.num_sideset += 1 + if self.pass_ > 0: + set_bit = 13 - self.sideset_count + self.prog[_PROG_DATA][-1] |= self.sideset_opt << 12 | value << set_bit + return self + + def wrap_target(self): + self.prog[_PROG_EXECCTRL] |= self.num_instr << 7 + + def wrap(self): + assert self.num_instr + self.prog[_PROG_EXECCTRL] |= (self.num_instr - 1) << 12 + self.wrap_used = True + + def label(self, label): + if self.pass_ == 0: + if label in self.labels: + raise PIOASMError("duplicate label {}".format(label)) + self.labels[label] = self.num_instr + + def word(self, instr, label=None): + self.num_instr += 1 + if self.pass_ > 0: + if label is None: + label = 0 + else: + if not label in self.labels: + raise PIOASMError("unknown label {}".format(label)) + label = self.labels[label] + self.prog[_PROG_DATA].append(instr | label) + return self + + def nop(self): + return self.word(0xA042) + + def jmp(self, cond, label=None): + if label is None: + label = cond + cond = 0 # always + return self.word(0x0000 | cond << 5, label) + + def wait(self, polarity, src, index): + if src == 6: + src = 1 # "pin" + elif src != 0: + src = 2 # "irq" + return self.word(0x2000 | polarity << 7 | src << 5 | index) + + def in_(self, src, data): + if not 0 < data <= 32: + raise PIOASMError("invalid bit count {}".format(data)) + return self.word(0x4000 | src << 5 | data & 0x1F) + + def out(self, dest, data): + if dest == 8: + dest = 7 # exec + if not 0 < data <= 32: + raise PIOASMError("invalid bit count {}".format(data)) + return self.word(0x6000 | dest << 5 | data & 0x1F) + + def push(self, value=0, value2=0): + value |= value2 + if not value & 1: + value |= 0x20 # block by default + return self.word(0x8000 | (value & 0x60)) + + def pull(self, value=0, value2=0): + value |= value2 + if not value & 1: + value |= 0x20 # block by default + return self.word(0x8080 | (value & 0x60)) + + def mov(self, dest, src): + if dest == 8: + dest = 4 # exec + return self.word(0xA000 | dest << 5 | src) + + def irq(self, mod, index=None): + if index is None: + index = mod + mod = 0 # no modifiers + return self.word(0xC000 | (mod & 0x60) | index) + + def set(self, dest, data): + return self.word(0xE000 | dest << 5 | data) + + +_pio_funcs = { + # source constants for wait + "gpio": 0, + # "pin": see below, translated to 1 + # "irq": see below function, translated to 2 + # source/dest constants for in_, out, mov, set + "pins": 0, + "x": 1, + "y": 2, + "null": 3, + "pindirs": 4, + "pc": 5, + "status": 5, + "isr": 6, + "osr": 7, + "exec": 8, # translated to 4 for mov, 7 for out + # operation functions for mov's src + "invert": lambda x: x | 0x08, + "reverse": lambda x: x | 0x10, + # jmp condition constants + "not_x": 1, + "x_dec": 2, + "not_y": 3, + "y_dec": 4, + "x_not_y": 5, + "pin": 6, + "not_osre": 7, + # constants for push, pull + "noblock": 0x01, + "block": 0x21, + "iffull": 0x40, + "ifempty": 0x40, + # constants and modifiers for irq + # "noblock": see above + # "block": see above + "clear": 0x40, + "rel": lambda x: x | 0x10, + # functions + "wrap_target": None, + "wrap": None, + "label": None, + "word": None, + "nop": None, + "jmp": None, + "wait": None, + "in_": None, + "out": None, + "push": None, + "pull": None, + "mov": None, + "irq": None, + "set": None, +} + + +def asm_pio(**kw): + emit = PIOASMEmit(**kw) + + def dec(f): + nonlocal emit + + gl = _pio_funcs + gl["wrap_target"] = emit.wrap_target + gl["wrap"] = emit.wrap + gl["label"] = emit.label + gl["word"] = emit.word + gl["nop"] = emit.nop + gl["jmp"] = emit.jmp + gl["wait"] = emit.wait + gl["in_"] = emit.in_ + gl["out"] = emit.out + gl["push"] = emit.push + gl["pull"] = emit.pull + gl["mov"] = emit.mov + gl["irq"] = emit.irq + gl["set"] = emit.set + + old_gl = f.__globals__.copy() + f.__globals__.clear() + f.__globals__.update(gl) + + emit.start_pass(0) + f() + + emit.start_pass(1) + f() + + f.__globals__.clear() + f.__globals__.update(old_gl) + + return emit.prog + + return dec + + +# sideset_count is inclusive of enable bit +def asm_pio_encode(instr, sideset_count): + emit = PIOASMEmit() + emit.delay_max = 31 + emit.sideset_count = sideset_count + if emit.sideset_count: + emit.delay_max >>= emit.sideset_count + emit.pass_ = 1 + emit.num_instr = 0 + emit.num_sideset = 0 + + gl = _pio_funcs + gl["nop"] = emit.nop + # gl["jmp"] = emit.jmp currently not supported + gl["wait"] = emit.wait + gl["in_"] = emit.in_ + gl["out"] = emit.out + gl["push"] = emit.push + gl["pull"] = emit.pull + gl["mov"] = emit.mov + gl["irq"] = emit.irq + gl["set"] = emit.set + + exec(instr, gl) + + if len(emit.prog[_PROG_DATA]) != 1: + raise PIOASMError("expecting exactly 1 instruction") + return emit.prog[_PROG_DATA][0] diff --git a/ports/rp2/moduos.c b/ports/rp2/moduos.c new file mode 100644 index 0000000000..7ee662b5a4 --- /dev/null +++ b/ports/rp2/moduos.c @@ -0,0 +1,103 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 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/objstr.h" +#include "py/runtime.h" +#include "extmod/vfs.h" +#include "extmod/vfs_fat.h" +#include "extmod/vfs_lfs.h" +#include "genhdr/mpversion.h" + +STATIC const qstr os_uname_info_fields[] = { + MP_QSTR_sysname, MP_QSTR_nodename, + MP_QSTR_release, MP_QSTR_version, MP_QSTR_machine +}; +STATIC const MP_DEFINE_STR_OBJ(os_uname_info_sysname_obj, MICROPY_PY_SYS_PLATFORM); +STATIC const MP_DEFINE_STR_OBJ(os_uname_info_nodename_obj, MICROPY_PY_SYS_PLATFORM); +STATIC const MP_DEFINE_STR_OBJ(os_uname_info_release_obj, MICROPY_VERSION_STRING); +STATIC const MP_DEFINE_STR_OBJ(os_uname_info_version_obj, MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE " (" MICROPY_BUILD_TYPE ")"); +STATIC const MP_DEFINE_STR_OBJ(os_uname_info_machine_obj, MICROPY_HW_BOARD_NAME " with " MICROPY_HW_MCU_NAME); + +STATIC MP_DEFINE_ATTRTUPLE( + os_uname_info_obj, + os_uname_info_fields, + 5, + (mp_obj_t)&os_uname_info_sysname_obj, + (mp_obj_t)&os_uname_info_nodename_obj, + (mp_obj_t)&os_uname_info_release_obj, + (mp_obj_t)&os_uname_info_version_obj, + (mp_obj_t)&os_uname_info_machine_obj + ); + +STATIC mp_obj_t os_uname(void) { + return (mp_obj_t)&os_uname_info_obj; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(os_uname_obj, os_uname); + +STATIC const mp_rom_map_elem_t os_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uos) }, + + { MP_ROM_QSTR(MP_QSTR_uname), MP_ROM_PTR(&os_uname_obj) }, + + #if MICROPY_VFS + { MP_ROM_QSTR(MP_QSTR_chdir), MP_ROM_PTR(&mp_vfs_chdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_getcwd), MP_ROM_PTR(&mp_vfs_getcwd_obj) }, + { MP_ROM_QSTR(MP_QSTR_listdir), MP_ROM_PTR(&mp_vfs_listdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&mp_vfs_mkdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&mp_vfs_remove_obj) }, + { MP_ROM_QSTR(MP_QSTR_rename), MP_ROM_PTR(&mp_vfs_rename_obj) }, + { MP_ROM_QSTR(MP_QSTR_rmdir), MP_ROM_PTR(&mp_vfs_rmdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&mp_vfs_stat_obj) }, + { MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&mp_vfs_statvfs_obj) }, + #endif + + // The following are MicroPython extensions. + + #if MICROPY_PY_OS_DUPTERM + { MP_ROM_QSTR(MP_QSTR_dupterm), MP_ROM_PTR(&mp_uos_dupterm_obj) }, + #endif + + #if MICROPY_VFS + { MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&mp_vfs_ilistdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_mount), MP_ROM_PTR(&mp_vfs_mount_obj) }, + { MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&mp_vfs_umount_obj) }, + #if MICROPY_VFS_FAT + { MP_ROM_QSTR(MP_QSTR_VfsFat), MP_ROM_PTR(&mp_fat_vfs_type) }, + #endif + #if MICROPY_VFS_LFS1 + { MP_ROM_QSTR(MP_QSTR_VfsLfs1), MP_ROM_PTR(&mp_type_vfs_lfs1) }, + #endif + #if MICROPY_VFS_LFS2 + { MP_ROM_QSTR(MP_QSTR_VfsLfs2), MP_ROM_PTR(&mp_type_vfs_lfs2) }, + #endif + #endif +}; +STATIC MP_DEFINE_CONST_DICT(os_module_globals, os_module_globals_table); + +const mp_obj_module_t mp_module_uos = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&os_module_globals, +}; diff --git a/ports/rp2/modutime.c b/ports/rp2/modutime.c new file mode 100644 index 0000000000..4835a0ee11 --- /dev/null +++ b/ports/rp2/modutime.c @@ -0,0 +1,127 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013-2021 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/runtime.h" +#include "lib/timeutils/timeutils.h" +#include "extmod/utime_mphal.h" +#include "hardware/rtc.h" + +// localtime([secs]) +// Convert a time expressed in seconds since the Epoch into an 8-tuple which +// contains: (year, month, mday, hour, minute, second, weekday, yearday) +// If secs is not provided or None, then the current time from is used. +STATIC mp_obj_t time_localtime(size_t n_args, const mp_obj_t *args) { + if (n_args == 0 || args[0] == mp_const_none) { + // Get current date and time. + datetime_t t; + rtc_get_datetime(&t); + mp_obj_t tuple[8] = { + mp_obj_new_int(t.year), + mp_obj_new_int(t.month), + mp_obj_new_int(t.day), + mp_obj_new_int(t.hour), + mp_obj_new_int(t.min), + mp_obj_new_int(t.sec), + mp_obj_new_int((t.dotw + 6) % 7), // convert 0=Sunday to 6=Sunday + mp_obj_new_int(timeutils_year_day(t.year, t.month, t.day)), + }; + return mp_obj_new_tuple(8, tuple); + } else { + // Convert given seconds to tuple. + mp_int_t seconds = mp_obj_get_int(args[0]); + timeutils_struct_time_t tm; + timeutils_seconds_since_epoch_to_struct_time(seconds, &tm); + mp_obj_t tuple[8] = { + tuple[0] = mp_obj_new_int(tm.tm_year), + tuple[1] = mp_obj_new_int(tm.tm_mon), + tuple[2] = mp_obj_new_int(tm.tm_mday), + tuple[3] = mp_obj_new_int(tm.tm_hour), + tuple[4] = mp_obj_new_int(tm.tm_min), + tuple[5] = mp_obj_new_int(tm.tm_sec), + tuple[6] = mp_obj_new_int(tm.tm_wday), + tuple[7] = mp_obj_new_int(tm.tm_yday), + }; + return mp_obj_new_tuple(8, tuple); + } +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(time_localtime_obj, 0, 1, time_localtime); + +// mktime() +// This is inverse function of localtime. It's argument is a full 8-tuple +// which expresses a time as per localtime. It returns an integer which is +// the number of seconds since the Epoch. +STATIC mp_obj_t time_mktime(mp_obj_t tuple) { + size_t len; + mp_obj_t *elem; + mp_obj_get_array(tuple, &len, &elem); + + // localtime generates a tuple of len 8. CPython uses 9, so we accept both. + if (len < 8 || len > 9) { + mp_raise_TypeError(MP_ERROR_TEXT("mktime needs a tuple of length 8 or 9")); + } + + return mp_obj_new_int_from_uint(timeutils_mktime(mp_obj_get_int(elem[0]), + mp_obj_get_int(elem[1]), mp_obj_get_int(elem[2]), mp_obj_get_int(elem[3]), + mp_obj_get_int(elem[4]), mp_obj_get_int(elem[5]))); +} +MP_DEFINE_CONST_FUN_OBJ_1(time_mktime_obj, time_mktime); + +// time() +// Return the number of seconds since the Epoch. +STATIC mp_obj_t time_time(void) { + datetime_t t; + rtc_get_datetime(&t); + return mp_obj_new_int_from_ull(timeutils_seconds_since_epoch(t.year, t.month, t.day, t.hour, t.min, t.sec)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(time_time_obj, time_time); + +STATIC const mp_rom_map_elem_t mp_module_time_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_utime) }, + + { MP_ROM_QSTR(MP_QSTR_gmtime), MP_ROM_PTR(&time_localtime_obj) }, + { MP_ROM_QSTR(MP_QSTR_localtime), MP_ROM_PTR(&time_localtime_obj) }, + { MP_ROM_QSTR(MP_QSTR_mktime), MP_ROM_PTR(&time_mktime_obj) }, + + { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&time_time_obj) }, + { MP_ROM_QSTR(MP_QSTR_time_ns), MP_ROM_PTR(&mp_utime_time_ns_obj) }, + + { MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&mp_utime_sleep_obj) }, + { MP_ROM_QSTR(MP_QSTR_sleep_ms), MP_ROM_PTR(&mp_utime_sleep_ms_obj) }, + { MP_ROM_QSTR(MP_QSTR_sleep_us), MP_ROM_PTR(&mp_utime_sleep_us_obj) }, + + { MP_ROM_QSTR(MP_QSTR_ticks_ms), MP_ROM_PTR(&mp_utime_ticks_ms_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_us), MP_ROM_PTR(&mp_utime_ticks_us_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_cpu), MP_ROM_PTR(&mp_utime_ticks_cpu_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_add), MP_ROM_PTR(&mp_utime_ticks_add_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_diff), MP_ROM_PTR(&mp_utime_ticks_diff_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_time_globals, mp_module_time_globals_table); + +const mp_obj_module_t mp_module_utime = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_time_globals, +}; diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h new file mode 100644 index 0000000000..d409ccfd4d --- /dev/null +++ b/ports/rp2/mpconfigport.h @@ -0,0 +1,196 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 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. + */ + +// Options controlling how MicroPython is built, overriding defaults in py/mpconfig.h + +#include +#include "hardware/spi.h" +#include "hardware/sync.h" +#include "pico/binary_info.h" + +// Board and hardware specific configuration +#define MICROPY_HW_BOARD_NAME "Raspberry Pi Pico" +#define MICROPY_HW_MCU_NAME "RP2040" +#define MICROPY_HW_ENABLE_UART_REPL (0) // useful if there is no USB +#define MICROPY_HW_ENABLE_USBDEV (1) + +// Memory allocation policies +#define MICROPY_GC_STACK_ENTRY_TYPE uint16_t +#define MICROPY_ALLOC_PATH_MAX (128) +#define MICROPY_QSTR_BYTES_IN_HASH (1) + +// MicroPython emitters +#define MICROPY_PERSISTENT_CODE_LOAD (1) +#define MICROPY_EMIT_THUMB (1) +#define MICROPY_EMIT_THUMB_ARMV7M (0) +#define MICROPY_EMIT_INLINE_THUMB (1) +#define MICROPY_EMIT_INLINE_THUMB_FLOAT (0) +#define MICROPY_EMIT_INLINE_THUMB_ARMV7M (0) + +// Python internal features +#define MICROPY_READER_VFS (1) +#define MICROPY_ENABLE_GC (1) +#define MICROPY_ENABLE_FINALISER (1) +#define MICROPY_STACK_CHECK (1) +#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) +#define MICROPY_KBD_EXCEPTION (1) +#define MICROPY_HELPER_REPL (1) +#define MICROPY_REPL_AUTO_INDENT (1) +#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) +#define MICROPY_ENABLE_SOURCE_LINE (1) +#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) +#define MICROPY_MODULE_BUILTIN_INIT (1) +#define MICROPY_MODULE_WEAK_LINKS (1) +#define MICROPY_CAN_OVERRIDE_BUILTINS (1) +#define MICROPY_ENABLE_SCHEDULER (1) +#define MICROPY_SCHEDULER_DEPTH (8) + +// Fine control over Python builtins, classes, modules, etc +#define MICROPY_PY_FUNCTION_ATTRS (1) +#define MICROPY_PY_BUILTINS_STR_UNICODE (1) +#define MICROPY_PY_BUILTINS_MEMORYVIEW (1) +#define MICROPY_PY_BUILTINS_ROUND_INT (1) +#define MICROPY_PY_ALL_SPECIAL_METHODS (1) +#define MICROPY_PY_BUILTINS_INPUT (1) +#define MICROPY_PY_BUILTINS_HELP (1) +#define MICROPY_PY_BUILTINS_HELP_TEXT rp2_help_text +#define MICROPY_PY_BUILTINS_HELP_MODULES (1) +#define MICROPY_PY_ARRAY_SLICE_ASSIGN (1) +#define MICROPY_PY___FILE__ (0) +#define MICROPY_PY_MICROPYTHON_MEM_INFO (1) +#define MICROPY_PY_IO_IOBASE (1) +#define MICROPY_PY_IO_FILEIO (1) +#define MICROPY_PY_SYS_MAXSIZE (1) +#define MICROPY_PY_SYS_STDFILES (1) +#define MICROPY_PY_SYS_STDIO_BUFFER (1) +#define MICROPY_PY_SYS_PLATFORM "rp2" +#define MICROPY_PY_THREAD (1) +#define MICROPY_PY_THREAD_GIL (0) + +// Extended modules +#define MICROPY_EPOCH_IS_1970 (1) +#define MICROPY_PY_UASYNCIO (1) +#define MICROPY_PY_UCTYPES (1) +#define MICROPY_PY_UZLIB (1) +#define MICROPY_PY_UJSON (1) +#define MICROPY_PY_URE (1) +#define MICROPY_PY_URE_MATCH_GROUPS (1) +#define MICROPY_PY_URE_MATCH_SPAN_START_END (1) +#define MICROPY_PY_URE_SUB (1) +#define MICROPY_PY_UHASHLIB (1) +#define MICROPY_PY_UBINASCII (1) +#define MICROPY_PY_UTIME_MP_HAL (1) +#define MICROPY_PY_URANDOM (1) +#define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) +#define MICROPY_PY_URANDOM_SEED_INIT_FUNC (rosc_random_u32()) +#define MICROPY_PY_USELECT (1) +#define MICROPY_PY_MACHINE (1) +#define MICROPY_PY_MACHINE_PIN_MAKE_NEW mp_pin_make_new +#define MICROPY_PY_MACHINE_I2C (1) +#define MICROPY_PY_MACHINE_SPI (1) +#define MICROPY_PY_MACHINE_SPI_MSB (SPI_MSB_FIRST) +#define MICROPY_PY_MACHINE_SPI_LSB (SPI_LSB_FIRST) +#define MICROPY_PY_FRAMEBUF (1) +#define MICROPY_VFS (1) +#define MICROPY_VFS_LFS2 (1) + +// Use VfsLfs2's types for fileio/textio +#define mp_type_fileio mp_type_vfs_lfs2_fileio +#define mp_type_textio mp_type_vfs_lfs2_textio + +// Use VFS's functions for import stat and builtin open +#define mp_import_stat mp_vfs_import_stat +#define mp_builtin_open_obj mp_vfs_open_obj + +// Hooks to add builtins + +#define MICROPY_PORT_BUILTINS \ + { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mp_builtin_open_obj) }, + +extern const struct _mp_obj_module_t mp_module_machine; +extern const struct _mp_obj_module_t mp_module_onewire; +extern const struct _mp_obj_module_t mp_module_rp2; +extern const struct _mp_obj_module_t mp_module_uos; +extern const struct _mp_obj_module_t mp_module_utime; + +#define MICROPY_PORT_BUILTIN_MODULES \ + { MP_OBJ_NEW_QSTR(MP_QSTR_machine), (mp_obj_t)&mp_module_machine }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR__onewire), (mp_obj_t)&mp_module_onewire }, \ + { MP_OBJ_NEW_QSTR(MP_QSTR__rp2), (mp_obj_t)&mp_module_rp2 }, \ + { MP_ROM_QSTR(MP_QSTR_uos), MP_ROM_PTR(&mp_module_uos) }, \ + { MP_ROM_QSTR(MP_QSTR_utime), MP_ROM_PTR(&mp_module_utime) }, \ + +#define MICROPY_PORT_ROOT_POINTERS \ + const char *readline_hist[8]; \ + void *machine_pin_irq_obj[30]; \ + void *rp2_pio_irq_obj[2]; \ + void *rp2_state_machine_irq_obj[8]; \ + +#define MP_STATE_PORT MP_STATE_VM + +// Miscellaneous settings + +// TODO need to look and see if these could/should be spinlock/mutex +#define MICROPY_BEGIN_ATOMIC_SECTION() save_and_disable_interrupts() +#define MICROPY_END_ATOMIC_SECTION(state) restore_interrupts(state) + +#if MICROPY_HW_ENABLE_USBDEV +#define MICROPY_HW_USBDEV_TASK_HOOK extern void tud_task(void); tud_task(); +#define MICROPY_VM_HOOK_COUNT (10) +#define MICROPY_VM_HOOK_INIT static uint vm_hook_divisor = MICROPY_VM_HOOK_COUNT; +#define MICROPY_VM_HOOK_POLL if (--vm_hook_divisor == 0) { \ + vm_hook_divisor = MICROPY_VM_HOOK_COUNT; \ + MICROPY_HW_USBDEV_TASK_HOOK \ +} +#define MICROPY_VM_HOOK_LOOP MICROPY_VM_HOOK_POLL +#define MICROPY_VM_HOOK_RETURN MICROPY_VM_HOOK_POLL +#else +#define MICROPY_HW_USBDEV_TASK_HOOK +#endif + +#define MICROPY_EVENT_POLL_HOOK \ + do { \ + extern void mp_handle_pending(bool); \ + mp_handle_pending(true); \ + best_effort_wfe_or_timeout(make_timeout_time_ms(1)); \ + MICROPY_HW_USBDEV_TASK_HOOK \ + } while (0); + +#define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) + +#define MP_SSIZE_MAX (0x7fffffff) +typedef intptr_t mp_int_t; // must be pointer size +typedef uintptr_t mp_uint_t; // must be pointer size +typedef intptr_t mp_off_t; + +// We need to provide a declaration/definition of alloca() +#include + +#define BINARY_INFO_TAG_MICROPYTHON BINARY_INFO_MAKE_TAG('M', 'P') +#define BINARY_INFO_ID_MP_FROZEN 0x4a99d719 +#define MICROPY_FROZEN_LIST_ITEM(name, file) bi_decl(bi_string(BINARY_INFO_TAG_MICROPYTHON, BINARY_INFO_ID_MP_FROZEN, name)) + +extern uint32_t rosc_random_u32(void); diff --git a/ports/rp2/mphalport.c b/ports/rp2/mphalport.c new file mode 100644 index 0000000000..1122afcef7 --- /dev/null +++ b/ports/rp2/mphalport.c @@ -0,0 +1,142 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 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/runtime.h" +#include "py/stream.h" +#include "py/mphal.h" +#include "lib/timeutils/timeutils.h" +#include "tusb.h" +#include "uart.h" +#include "hardware/rtc.h" + +#if MICROPY_HW_ENABLE_UART_REPL + +#ifndef UART_BUFFER_LEN +// reasonably big so we can paste +#define UART_BUFFER_LEN 256 +#endif + +STATIC uint8_t stdin_ringbuf_array[UART_BUFFER_LEN]; +ringbuf_t stdin_ringbuf = { stdin_ringbuf_array, sizeof(stdin_ringbuf_array) }; + +#endif + +#if MICROPY_KBD_EXCEPTION + +int mp_interrupt_char = -1; + +void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char) { + (void)itf; + (void)wanted_char; + tud_cdc_read_char(); // discard interrupt char + mp_keyboard_interrupt(); +} + +void mp_hal_set_interrupt_char(int c) { + mp_interrupt_char = c; + tud_cdc_set_wanted_char(c); +} + +#endif + +uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { + uintptr_t ret = 0; + #if MICROPY_HW_ENABLE_UART_REPL + if ((poll_flags & MP_STREAM_POLL_RD) && ringbuf_peek(&stdin_ringbuf) != -1) { + ret |= MP_STREAM_POLL_RD; + } + #endif + #if MICROPY_HW_ENABLE_USBDEV + if (tud_cdc_connected() && tud_cdc_available()) { + ret |= MP_STREAM_POLL_RD; + } + #endif + return ret; +} + +// Receive single character +int mp_hal_stdin_rx_chr(void) { + for (;;) { + #if MICROPY_HW_ENABLE_UART_REPL + int c = ringbuf_get(&stdin_ringbuf); + if (c != -1) { + return c; + } + #endif + #if MICROPY_HW_ENABLE_USBDEV + if (tud_cdc_connected() && tud_cdc_available()) { + uint8_t buf[1]; + uint32_t count = tud_cdc_read(buf, sizeof(buf)); + if (count) { + return buf[0]; + } + } + #endif + MICROPY_EVENT_POLL_HOOK + } +} + +// Send string of given length +void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { + #if MICROPY_HW_ENABLE_UART_REPL + mp_uart_write_strn(str, len); + #endif + + #if MICROPY_HW_ENABLE_USBDEV + if (tud_cdc_connected()) { + for (size_t i = 0; i < len;) { + uint32_t n = len - i; + if (n > CFG_TUD_CDC_EP_BUFSIZE) { + n = CFG_TUD_CDC_EP_BUFSIZE; + } + while (n > tud_cdc_write_available()) { + tud_task(); + tud_cdc_write_flush(); + } + uint32_t n2 = tud_cdc_write(str + i, n); + tud_task(); + tud_cdc_write_flush(); + i += n2; + } + } + #endif +} + +void mp_hal_delay_ms(mp_uint_t ms) { + absolute_time_t t = make_timeout_time_ms(ms); + while (!time_reached(t)) { + mp_handle_pending(true); + best_effort_wfe_or_timeout(t); + MICROPY_HW_USBDEV_TASK_HOOK + } +} + +uint64_t mp_hal_time_ns(void) { + datetime_t t; + rtc_get_datetime(&t); + uint64_t s = timeutils_seconds_since_epoch(t.year, t.month, t.day, t.hour, t.min, t.sec); + return s * 1000000000ULL; +} diff --git a/ports/rp2/mphalport.h b/ports/rp2/mphalport.h new file mode 100644 index 0000000000..40633dcf2c --- /dev/null +++ b/ports/rp2/mphalport.h @@ -0,0 +1,113 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 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_RP2_MPHALPORT_H +#define MICROPY_INCLUDED_RP2_MPHALPORT_H + +#include "py/mpconfig.h" +#include "py/ringbuf.h" +#include "pico/time.h" + +extern int mp_interrupt_char; +extern ringbuf_t stdin_ringbuf; + +void mp_hal_set_interrupt_char(int c); + +static inline void mp_hal_delay_us(mp_uint_t us) { + sleep_us(us); +} + +static inline void mp_hal_delay_us_fast(mp_uint_t us) { + busy_wait_us(us); +} + +#define mp_hal_quiet_timing_enter() MICROPY_BEGIN_ATOMIC_SECTION() +#define mp_hal_quiet_timing_exit(irq_state) MICROPY_END_ATOMIC_SECTION(irq_state) + +static inline mp_uint_t mp_hal_ticks_us(void) { + return time_us_32(); +} + +static inline mp_uint_t mp_hal_ticks_ms(void) { + return to_ms_since_boot(get_absolute_time()); +} + +static inline mp_uint_t mp_hal_ticks_cpu(void) { + // ticks_cpu() is defined as using the highest-resolution timing source + // in the system. This is usually a CPU clock, but doesn't have to be. + return time_us_32(); +} + +// C-level pin HAL + +#include "py/obj.h" +#include "hardware/gpio.h" + +#define MP_HAL_PIN_FMT "%u" +#define mp_hal_pin_obj_t uint + +extern uint32_t machine_pin_open_drain_mask; + +mp_hal_pin_obj_t mp_hal_get_pin_obj(mp_obj_t pin_in); + +static inline unsigned int mp_hal_pin_name(mp_hal_pin_obj_t pin) { + return pin; +} + +static inline void mp_hal_pin_input(mp_hal_pin_obj_t pin) { + gpio_set_function(pin, GPIO_FUNC_SIO); + gpio_set_dir(pin, GPIO_IN); + machine_pin_open_drain_mask &= ~(1 << pin); +} + +static inline void mp_hal_pin_output(mp_hal_pin_obj_t pin) { + gpio_set_function(pin, GPIO_FUNC_SIO); + gpio_set_dir(pin, GPIO_OUT); + machine_pin_open_drain_mask &= ~(1 << pin); +} + +static inline void mp_hal_pin_open_drain(mp_hal_pin_obj_t pin) { + gpio_set_function(pin, GPIO_FUNC_SIO); + gpio_set_dir(pin, GPIO_IN); + gpio_put(pin, 0); + machine_pin_open_drain_mask |= 1 << pin; +} + +static inline int mp_hal_pin_read(mp_hal_pin_obj_t pin) { + return gpio_get(pin); +} + +static inline void mp_hal_pin_write(mp_hal_pin_obj_t pin, int v) { + gpio_put(pin, v); +} + +static inline void mp_hal_pin_od_low(mp_hal_pin_obj_t pin) { + gpio_set_dir(pin, GPIO_OUT); +} + +static inline void mp_hal_pin_od_high(mp_hal_pin_obj_t pin) { + gpio_set_dir(pin, GPIO_IN); +} + +#endif // MICROPY_INCLUDED_RP2_MPHALPORT_H diff --git a/ports/rp2/mpthreadport.c b/ports/rp2/mpthreadport.c new file mode 100644 index 0000000000..fb4428772a --- /dev/null +++ b/ports/rp2/mpthreadport.c @@ -0,0 +1,105 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 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/runtime.h" +#include "py/gc.h" +#include "py/mpthread.h" +#include "pico/stdlib.h" +#include "pico/multicore.h" + +#if MICROPY_PY_THREAD + +extern uint8_t __StackTop, __StackBottom; + +void *core_state[2]; + +STATIC void *(*core1_entry)(void *) = NULL; +STATIC void *core1_arg = NULL; +STATIC uint32_t *core1_stack = NULL; +STATIC size_t core1_stack_num_words = 0; + +void mp_thread_init(void) { + mp_thread_set_state(&mp_state_ctx.thread); + core1_entry = NULL; +} + +void mp_thread_gc_others(void) { + if (get_core_num() == 0) { + // GC running on core0, trace core1's stack, if it's running. + if (core1_entry != NULL) { + gc_collect_root((void **)core1_stack, core1_stack_num_words); + } + } else { + // GC running on core1, trace core0's stack. + gc_collect_root((void **)&__StackBottom, (&__StackTop - &__StackBottom) / sizeof(uintptr_t)); + } +} + +STATIC void core1_entry_wrapper(void) { + if (core1_entry) { + core1_entry(core1_arg); + } + core1_entry = NULL; + // returning from here will loop the core forever (WFI) +} + +void mp_thread_create(void *(*entry)(void *), void *arg, size_t *stack_size) { + // Check if core1 is already in use. + if (core1_entry != NULL) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("core1 in use")); + } + + core1_entry = entry; + core1_arg = arg; + + if (*stack_size == 0) { + *stack_size = 4096; // default stack size + } else if (*stack_size < 2048) { + *stack_size = 2048; // minimum stack size + } + + // Round stack size to a multiple of the word size. + core1_stack_num_words = *stack_size / sizeof(uint32_t); + *stack_size = core1_stack_num_words * sizeof(uint32_t); + + // Allocate stack. + core1_stack = m_new(uint32_t, core1_stack_num_words); + + // Create thread on core1. + multicore_reset_core1(); + multicore_launch_core1_with_stack(core1_entry_wrapper, core1_stack, *stack_size); + + // Adjust stack_size to provide room to recover from hitting the limit. + *stack_size -= 512; +} + +void mp_thread_start(void) { +} + +void mp_thread_finish(void) { +} + +#endif // MICROPY_PY_THREAD diff --git a/ports/rp2/mpthreadport.h b/ports/rp2/mpthreadport.h new file mode 100644 index 0000000000..4583f6f539 --- /dev/null +++ b/ports/rp2/mpthreadport.h @@ -0,0 +1,64 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 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_RP2_MPTHREADPORT_H +#define MICROPY_INCLUDED_RP2_MPTHREADPORT_H + +#include "py/mpthread.h" +#include "pico/mutex.h" + +typedef struct mutex mp_thread_mutex_t; + +extern void *core_state[2]; + +void mp_thread_init(void); +void mp_thread_gc_others(void); + +static inline void mp_thread_set_state(struct _mp_state_thread_t *state) { + core_state[get_core_num()] = state; +} + +static inline struct _mp_state_thread_t *mp_thread_get_state(void) { + return core_state[get_core_num()]; +} + +static inline void mp_thread_mutex_init(mp_thread_mutex_t *m) { + mutex_init(m); +} + +static inline int mp_thread_mutex_lock(mp_thread_mutex_t *m, int wait) { + if (wait) { + mutex_enter_blocking(m); + return 1; + } else { + return mutex_try_enter(m, NULL); + } +} + +static inline void mp_thread_mutex_unlock(mp_thread_mutex_t *m) { + mutex_exit(m); +} + +#endif // MICROPY_INCLUDED_RP2_MPTHREADPORT_H diff --git a/ports/rp2/qstrdefsport.h b/ports/rp2/qstrdefsport.h new file mode 100644 index 0000000000..472d05f437 --- /dev/null +++ b/ports/rp2/qstrdefsport.h @@ -0,0 +1,3 @@ +// qstrs specific to this port +// *FORMAT-OFF* +Q(/lib) diff --git a/ports/rp2/rp2_flash.c b/ports/rp2/rp2_flash.c new file mode 100644 index 0000000000..cd1bc65489 --- /dev/null +++ b/ports/rp2/rp2_flash.c @@ -0,0 +1,146 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/runtime.h" +#include "extmod/vfs.h" +#include "modrp2.h" +#include "hardware/flash.h" +#include "pico/binary_info.h" + +#define BLOCK_SIZE_BYTES (FLASH_SECTOR_SIZE) + +#ifndef MICROPY_HW_FLASH_STORAGE_BYTES +#define MICROPY_HW_FLASH_STORAGE_BYTES (1408 * 1024) +#endif + +#ifndef MICROPY_HW_FLASH_STORAGE_BASE +#define MICROPY_HW_FLASH_STORAGE_BASE (PICO_FLASH_SIZE_BYTES - MICROPY_HW_FLASH_STORAGE_BYTES) +#endif + +static_assert(MICROPY_HW_FLASH_STORAGE_BASE + MICROPY_HW_FLASH_STORAGE_BYTES <= PICO_FLASH_SIZE_BYTES, "MICROPY_HW_FLASH_STORAGE_BYTES too big"); + +typedef struct _rp2_flash_obj_t { + mp_obj_base_t base; + uint32_t flash_base; + uint32_t flash_size; +} rp2_flash_obj_t; + +STATIC rp2_flash_obj_t rp2_flash_obj = { + .base = { &rp2_flash_type }, + .flash_base = MICROPY_HW_FLASH_STORAGE_BASE, + .flash_size = MICROPY_HW_FLASH_STORAGE_BYTES, +}; + +// Tag the flash drive in the binary as readable/writable (but not reformatable) +bi_decl(bi_block_device( + BINARY_INFO_TAG_MICROPYTHON, + "MicroPython", + XIP_BASE + MICROPY_HW_FLASH_STORAGE_BASE, + MICROPY_HW_FLASH_STORAGE_BYTES, + NULL, + BINARY_INFO_BLOCK_DEV_FLAG_READ | + BINARY_INFO_BLOCK_DEV_FLAG_WRITE | + BINARY_INFO_BLOCK_DEV_FLAG_PT_UNKNOWN)); + +STATIC mp_obj_t rp2_flash_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + // Check args. + mp_arg_check_num(n_args, n_kw, 0, 0, false); + + // Return singleton object. + return MP_OBJ_FROM_PTR(&rp2_flash_obj); +} + +STATIC mp_obj_t rp2_flash_readblocks(size_t n_args, const mp_obj_t *args) { + rp2_flash_obj_t *self = MP_OBJ_TO_PTR(args[0]); + uint32_t offset = mp_obj_get_int(args[1]) * BLOCK_SIZE_BYTES; + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_WRITE); + if (n_args == 4) { + offset += mp_obj_get_int(args[3]); + } + memcpy(bufinfo.buf, (void *)(XIP_BASE + self->flash_base + offset), bufinfo.len); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(rp2_flash_readblocks_obj, 3, 4, rp2_flash_readblocks); + +STATIC mp_obj_t rp2_flash_writeblocks(size_t n_args, const mp_obj_t *args) { + rp2_flash_obj_t *self = MP_OBJ_TO_PTR(args[0]); + uint32_t offset = mp_obj_get_int(args[1]) * BLOCK_SIZE_BYTES; + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ); + if (n_args == 3) { + flash_range_erase(self->flash_base + offset, bufinfo.len); + // TODO check return value + } else { + offset += mp_obj_get_int(args[3]); + } + flash_range_program(self->flash_base + offset, bufinfo.buf, bufinfo.len); + // TODO check return value + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(rp2_flash_writeblocks_obj, 3, 4, rp2_flash_writeblocks); + +STATIC mp_obj_t rp2_flash_ioctl(mp_obj_t self_in, mp_obj_t cmd_in, mp_obj_t arg_in) { + rp2_flash_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_int_t cmd = mp_obj_get_int(cmd_in); + switch (cmd) { + case MP_BLOCKDEV_IOCTL_INIT: + return MP_OBJ_NEW_SMALL_INT(0); + case MP_BLOCKDEV_IOCTL_DEINIT: + return MP_OBJ_NEW_SMALL_INT(0); + case MP_BLOCKDEV_IOCTL_SYNC: + return MP_OBJ_NEW_SMALL_INT(0); + case MP_BLOCKDEV_IOCTL_BLOCK_COUNT: + return MP_OBJ_NEW_SMALL_INT(self->flash_size / BLOCK_SIZE_BYTES); + case MP_BLOCKDEV_IOCTL_BLOCK_SIZE: + return MP_OBJ_NEW_SMALL_INT(BLOCK_SIZE_BYTES); + case MP_BLOCKDEV_IOCTL_BLOCK_ERASE: { + uint32_t offset = mp_obj_get_int(arg_in) * BLOCK_SIZE_BYTES; + flash_range_erase(self->flash_base + offset, BLOCK_SIZE_BYTES); + // TODO check return value + return MP_OBJ_NEW_SMALL_INT(0); + } + default: + return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(rp2_flash_ioctl_obj, rp2_flash_ioctl); + +STATIC const mp_rom_map_elem_t rp2_flash_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&rp2_flash_readblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&rp2_flash_writeblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&rp2_flash_ioctl_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(rp2_flash_locals_dict, rp2_flash_locals_dict_table); + +const mp_obj_type_t rp2_flash_type = { + { &mp_type_type }, + .name = MP_QSTR_Flash, + .make_new = rp2_flash_make_new, + .locals_dict = (mp_obj_dict_t *)&rp2_flash_locals_dict, +}; diff --git a/ports/rp2/rp2_pio.c b/ports/rp2/rp2_pio.c new file mode 100644 index 0000000000..c8542127c0 --- /dev/null +++ b/ports/rp2/rp2_pio.c @@ -0,0 +1,780 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/binary.h" +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" +#include "lib/utils/mpirq.h" +#include "modrp2.h" + +#include "hardware/clocks.h" +#include "hardware/irq.h" +#include "hardware/pio.h" + +#define PIO_NUM(pio) ((pio) == pio0 ? 0 : 1) + +typedef struct _rp2_pio_obj_t { + mp_obj_base_t base; + PIO pio; + uint8_t irq; +} rp2_pio_obj_t; + +typedef struct _rp2_pio_irq_obj_t { + mp_irq_obj_t base; + uint32_t flags; + uint32_t trigger; +} rp2_pio_irq_obj_t; + +typedef struct _rp2_state_machine_obj_t { + mp_obj_base_t base; + PIO pio; + uint8_t irq; + uint8_t sm; // 0-3 + uint8_t id; // 0-7 +} rp2_state_machine_obj_t; + +typedef struct _rp2_state_machine_irq_obj_t { + mp_irq_obj_t base; + uint8_t flags; + uint8_t trigger; +} rp2_state_machine_irq_obj_t; + +STATIC const rp2_state_machine_obj_t rp2_state_machine_obj[8]; + +STATIC mp_obj_t rp2_state_machine_init_helper(const rp2_state_machine_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); + +STATIC void pio_irq0(PIO pio) { + uint32_t ints = pio->ints0; + + // Acknowledge SM0-3 IRQs if they are enabled on this IRQ0. + pio->irq = ints >> 8; + + // Call handler if it is registered, for PIO irqs. + rp2_pio_irq_obj_t *irq = MP_STATE_PORT(rp2_pio_irq_obj[PIO_NUM(pio)]); + if (irq != NULL && (ints & irq->trigger)) { + irq->flags = ints & irq->trigger; + mp_irq_handler(&irq->base); + } + + // Call handler if it is registered, for StateMachine irqs. + for (size_t i = 0; i < 4; ++i) { + rp2_state_machine_irq_obj_t *irq = MP_STATE_PORT(rp2_state_machine_irq_obj[PIO_NUM(pio) * 4 + i]); + if (irq != NULL && ((ints >> (8 + i)) & irq->trigger)) { + irq->flags = 1; + mp_irq_handler(&irq->base); + } + } +} + +STATIC void pio0_irq0(void) { + pio_irq0(pio0); +} + +STATIC void pio1_irq0(void) { + pio_irq0(pio1); +} + +void rp2_pio_init(void) { + // Reset all PIO instruction memory. + pio_clear_instruction_memory(pio0); + pio_clear_instruction_memory(pio1); + + // Set up interrupts. + memset(MP_STATE_PORT(rp2_pio_irq_obj), 0, sizeof(MP_STATE_PORT(rp2_pio_irq_obj))); + memset(MP_STATE_PORT(rp2_state_machine_irq_obj), 0, sizeof(MP_STATE_PORT(rp2_state_machine_irq_obj))); + irq_set_exclusive_handler(PIO0_IRQ_0, pio0_irq0); + irq_set_exclusive_handler(PIO1_IRQ_0, pio1_irq0); +} + +void rp2_pio_deinit(void) { + // Disable and clear interrupts. + irq_set_mask_enabled((1u << PIO0_IRQ_0) | (1u << PIO0_IRQ_1), false); + irq_remove_handler(PIO0_IRQ_0, pio0_irq0); + irq_remove_handler(PIO1_IRQ_0, pio1_irq0); +} + +/******************************************************************************/ +// Helper functions to manage asm_pio data structure. + +#define ASM_PIO_CONFIG_DEFAULT { -1, 0, 0, 0 }; + +enum { + PROG_DATA, + PROG_OFFSET_PIO0, + PROG_OFFSET_PIO1, + PROG_EXECCTRL, + PROG_SHIFTCTRL, + PROG_OUT_PINS, + PROG_SET_PINS, + PROG_SIDESET_PINS, + PROG_MAX_FIELDS, +}; + +typedef struct _asm_pio_config_t { + int8_t base; + uint8_t count; + uint8_t pindirs; + uint8_t pinvals; +} asm_pio_config_t; + +STATIC void asm_pio_override_shiftctrl(mp_obj_t arg, uint32_t bits, uint32_t lsb, pio_sm_config *config) { + if (arg != mp_const_none) { + config->shiftctrl = (config->shiftctrl & ~bits) | (mp_obj_get_int(arg) << lsb); + } +} + +STATIC void asm_pio_get_pins(const char *type, mp_obj_t prog_pins, mp_obj_t arg_base, asm_pio_config_t *config) { + if (prog_pins != mp_const_none) { + // The PIO program specified pins for initialisation on out/set/sideset. + if (mp_obj_is_integer(prog_pins)) { + // A single pin specified, set its dir and value. + config->count = 1; + mp_int_t value = mp_obj_get_int(prog_pins); + config->pindirs = value >> 1; + config->pinvals = value & 1; + } else { + // An array of pins specified, set their dirs and values. + size_t count; + mp_obj_t *items; + mp_obj_get_array(prog_pins, &count, &items); + config->count = count; + for (size_t i = 0; i < config->count; ++i) { + mp_int_t value = mp_obj_get_int(items[i]); + config->pindirs |= (value >> 1) << i; + config->pinvals |= (value & 1) << i; + } + } + } + + if (arg_base != mp_const_none) { + // The instantiation of the PIO program specified a base pin. + config->base = mp_hal_get_pin_obj(arg_base); + } +} + +STATIC void asm_pio_init_gpio(PIO pio, uint32_t sm, asm_pio_config_t *config) { + uint32_t pinmask = ((1 << config->count) - 1) << config->base; + pio_sm_set_pins_with_mask(pio, sm, config->pinvals << config->base, pinmask); + pio_sm_set_pindirs_with_mask(pio, sm, config->pindirs << config->base, pinmask); + for (size_t i = 0; i < config->count; ++i) { + gpio_set_function(config->base + i, pio == pio0 ? GPIO_FUNC_PIO0 : GPIO_FUNC_PIO1); + } +} + +/******************************************************************************/ +// PIO object + +STATIC const mp_irq_methods_t rp2_pio_irq_methods; + +STATIC rp2_pio_obj_t rp2_pio_obj[] = { + { { &rp2_pio_type }, pio0, PIO0_IRQ_0 }, + { { &rp2_pio_type }, pio1, PIO1_IRQ_0 }, +}; + +STATIC void rp2_pio_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + rp2_pio_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "PIO(%u)", self->pio == pio0 ? 0 : 1); +} + +// constructor(id) +STATIC mp_obj_t rp2_pio_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, 1, false); + + // Get the PIO object. + int pio_id = mp_obj_get_int(args[0]); + if (!(0 <= pio_id && pio_id < MP_ARRAY_SIZE(rp2_pio_obj))) { + mp_raise_ValueError("invalid PIO"); + } + const rp2_pio_obj_t *self = &rp2_pio_obj[pio_id]; + + // Return the PIO object. + return MP_OBJ_FROM_PTR(self); +} + +// PIO.add_program(prog) +STATIC mp_obj_t rp2_pio_add_program(mp_obj_t self_in, mp_obj_t prog_in) { + rp2_pio_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // Get the program data. + mp_obj_t *prog; + mp_obj_get_array_fixed_n(prog_in, PROG_MAX_FIELDS, &prog); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(prog[PROG_DATA], &bufinfo, MP_BUFFER_READ); + + // Add the program data to the PIO instruction memory. + struct pio_program pio_program = { bufinfo.buf, bufinfo.len / 2, -1 }; + if (!pio_can_add_program(self->pio, &pio_program)) { + mp_raise_OSError(MP_ENOMEM); + } + uint offset = pio_add_program(self->pio, &pio_program); + + // Store the program offset in the program object. + prog[PROG_OFFSET_PIO0 + PIO_NUM(self->pio)] = MP_OBJ_NEW_SMALL_INT(offset); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(rp2_pio_add_program_obj, rp2_pio_add_program); + +// PIO.remove_program([prog]) +STATIC mp_obj_t rp2_pio_remove_program(size_t n_args, const mp_obj_t *args) { + rp2_pio_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + // Default to remove all programs. + uint8_t length = 32; + uint offset = 0; + + if (n_args > 1) { + // Get specific program to remove. + mp_obj_t *prog; + mp_obj_get_array_fixed_n(args[1], PROG_MAX_FIELDS, &prog); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(prog[PROG_DATA], &bufinfo, MP_BUFFER_READ); + length = bufinfo.len / 2; + offset = mp_obj_get_int(prog[PROG_OFFSET_PIO0 + PIO_NUM(self->pio)]); + if (offset < 0) { + mp_raise_ValueError("prog not in instruction memory"); + } + // Invalidate the program offset in the program object. + prog[PROG_OFFSET_PIO0 + PIO_NUM(self->pio)] = MP_OBJ_NEW_SMALL_INT(-1); + } + + // Remove the program from the instruction memory. + struct pio_program pio_program = { NULL, length, -1 }; + pio_remove_program(self->pio, &pio_program, offset); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(rp2_pio_remove_program_obj, 1, 2, rp2_pio_remove_program); + +// PIO.state_machine(id, prog, freq=-1, *, set=None) +STATIC mp_obj_t rp2_pio_state_machine(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + rp2_pio_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + + // Get and verify the state machine id. + mp_int_t sm_id = mp_obj_get_int(pos_args[1]); + if (!(0 <= sm_id && sm_id < 4)) { + mp_raise_ValueError("invalide state machine"); + } + + // Return the correct StateMachine object. + const rp2_state_machine_obj_t *sm = &rp2_state_machine_obj[(self->pio == pio0 ? 0 : 4) + sm_id]; + + if (n_args > 2 || kw_args->used > 0) { + // Configuration arguments given so init this StateMachine. + rp2_state_machine_init_helper(sm, n_args - 2, pos_args + 2, kw_args); + } + + return MP_OBJ_FROM_PTR(sm); +} +MP_DEFINE_CONST_FUN_OBJ_KW(rp2_pio_state_machine_obj, 2, rp2_pio_state_machine); + +// PIO.irq(handler=None, trigger=IRQ_SM0|IRQ_SM1|IRQ_SM2|IRQ_SM3, hard=False) +STATIC mp_obj_t rp2_pio_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_handler, ARG_trigger, ARG_hard }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_handler, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_trigger, MP_ARG_INT, {.u_int = 0xf00} }, + { MP_QSTR_hard, MP_ARG_BOOL, {.u_bool = false} }, + }; + + // Parse the arguments. + rp2_pio_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Get the IRQ object. + rp2_pio_irq_obj_t *irq = MP_STATE_PORT(rp2_pio_irq_obj[PIO_NUM(self->pio)]); + + // Allocate the IRQ object if it doesn't already exist. + if (irq == NULL) { + irq = m_new_obj(rp2_pio_irq_obj_t); + irq->base.base.type = &mp_irq_type; + irq->base.methods = (mp_irq_methods_t *)&rp2_pio_irq_methods; + irq->base.parent = MP_OBJ_FROM_PTR(self); + irq->base.handler = mp_const_none; + irq->base.ishard = false; + MP_STATE_PORT(rp2_pio_irq_obj[PIO_NUM(self->pio)]) = irq; + } + + if (n_args > 1 || kw_args->used != 0) { + // Configure IRQ. + + // Disable all IRQs while data is updated. + irq_set_enabled(self->irq, false); + + // Update IRQ data. + irq->base.handler = args[ARG_handler].u_obj; + irq->base.ishard = args[ARG_hard].u_bool; + irq->flags = 0; + irq->trigger = args[ARG_trigger].u_int; + + // Enable IRQ if a handler is given. + if (args[ARG_handler].u_obj != mp_const_none) { + self->pio->inte0 = irq->trigger; + irq_set_enabled(self->irq, true); + } + } + + return MP_OBJ_FROM_PTR(irq); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(rp2_pio_irq_obj, 1, rp2_pio_irq); + +STATIC const mp_rom_map_elem_t rp2_pio_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_add_program), MP_ROM_PTR(&rp2_pio_add_program_obj) }, + { MP_ROM_QSTR(MP_QSTR_remove_program), MP_ROM_PTR(&rp2_pio_remove_program_obj) }, + { MP_ROM_QSTR(MP_QSTR_state_machine), MP_ROM_PTR(&rp2_pio_state_machine_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&rp2_pio_irq_obj) }, + + { MP_ROM_QSTR(MP_QSTR_IN_LOW), MP_ROM_INT(0) }, + { MP_ROM_QSTR(MP_QSTR_IN_HIGH), MP_ROM_INT(1) }, + { MP_ROM_QSTR(MP_QSTR_OUT_LOW), MP_ROM_INT(2) }, + { MP_ROM_QSTR(MP_QSTR_OUT_HIGH), MP_ROM_INT(3) }, + + { MP_ROM_QSTR(MP_QSTR_SHIFT_LEFT), MP_ROM_INT(0) }, + { MP_ROM_QSTR(MP_QSTR_SHIFT_RIGHT), MP_ROM_INT(1) }, + + { MP_ROM_QSTR(MP_QSTR_IRQ_SM0), MP_ROM_INT(0x100) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_SM1), MP_ROM_INT(0x200) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_SM2), MP_ROM_INT(0x400) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_SM3), MP_ROM_INT(0x800) }, +}; +STATIC MP_DEFINE_CONST_DICT(rp2_pio_locals_dict, rp2_pio_locals_dict_table); + +const mp_obj_type_t rp2_pio_type = { + { &mp_type_type }, + .name = MP_QSTR_PIO, + .print = rp2_pio_print, + .make_new = rp2_pio_make_new, + .locals_dict = (mp_obj_dict_t *)&rp2_pio_locals_dict, +}; + +STATIC mp_uint_t rp2_pio_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { + rp2_pio_obj_t *self = MP_OBJ_TO_PTR(self_in); + rp2_pio_irq_obj_t *irq = MP_STATE_PORT(rp2_pio_irq_obj[PIO_NUM(self->pio)]); + irq_set_enabled(self->irq, false); + irq->flags = 0; + irq->trigger = new_trigger; + irq_set_enabled(self->irq, true); + return 0; +} + +STATIC mp_uint_t rp2_pio_irq_info(mp_obj_t self_in, mp_uint_t info_type) { + rp2_pio_obj_t *self = MP_OBJ_TO_PTR(self_in); + rp2_pio_irq_obj_t *irq = MP_STATE_PORT(rp2_pio_irq_obj[PIO_NUM(self->pio)]); + if (info_type == MP_IRQ_INFO_FLAGS) { + return irq->flags; + } else if (info_type == MP_IRQ_INFO_TRIGGERS) { + return irq->trigger; + } + return 0; +} + +STATIC const mp_irq_methods_t rp2_pio_irq_methods = { + .trigger = rp2_pio_irq_trigger, + .info = rp2_pio_irq_info, +}; + +/******************************************************************************/ +// StateMachine object + +STATIC const mp_irq_methods_t rp2_state_machine_irq_methods; + +STATIC const rp2_state_machine_obj_t rp2_state_machine_obj[] = { + { { &rp2_state_machine_type }, pio0, PIO0_IRQ_0, 0, 0 }, + { { &rp2_state_machine_type }, pio0, PIO0_IRQ_0, 1, 1 }, + { { &rp2_state_machine_type }, pio0, PIO0_IRQ_0, 2, 2 }, + { { &rp2_state_machine_type }, pio0, PIO0_IRQ_0, 3, 3 }, + { { &rp2_state_machine_type }, pio1, PIO1_IRQ_0, 0, 4 }, + { { &rp2_state_machine_type }, pio1, PIO1_IRQ_0, 1, 5 }, + { { &rp2_state_machine_type }, pio1, PIO1_IRQ_0, 2, 6 }, + { { &rp2_state_machine_type }, pio1, PIO1_IRQ_0, 3, 7 }, +}; + +STATIC void rp2_state_machine_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + rp2_state_machine_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "StateMachine(%u)", self->id); +} + +// StateMachine.init(prog, freq=-1, *, +// in_base=None, out_base=None, set_base=None, sideset_base=None, +// in_shiftdir=None, out_shiftdir=None, push_thresh=None, pull_thresh=None, +// ) +STATIC mp_obj_t rp2_state_machine_init_helper(const rp2_state_machine_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { + ARG_prog, ARG_freq, + ARG_in_base, ARG_out_base, ARG_set_base, ARG_sideset_base, + ARG_in_shiftdir, ARG_out_shiftdir, ARG_push_thresh, ARG_pull_thresh + }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_prog, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_freq, MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_in_base, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_out_base, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_set_base, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_sideset_base, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_in_shiftdir, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_out_shiftdir, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_push_thresh, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_pull_thresh, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + }; + + // Parse the arguments. + 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); + + // Get the program. + mp_obj_t *prog; + mp_obj_get_array_fixed_n(args[ARG_prog].u_obj, PROG_MAX_FIELDS, &prog); + + // Get and the program offset, and load it into memory if it's not already there. + mp_int_t offset = mp_obj_get_int(prog[PROG_OFFSET_PIO0 + PIO_NUM(self->pio)]); + if (offset < 0) { + rp2_pio_add_program(&rp2_pio_obj[PIO_NUM(self->pio)], args[ARG_prog].u_obj); + offset = mp_obj_get_int(prog[PROG_OFFSET_PIO0 + PIO_NUM(self->pio)]); + } + + // Compute the clock divider. + float div; + if (args[ARG_freq].u_int < 0) { + div = 1; + } else if (args[ARG_freq].u_int == 0) { + div = 0; + } else { + div = (float)clock_get_hz(clk_sys) / (float)args[ARG_freq].u_int; + } + + // Disable and reset the state machine. + pio_sm_init(self->pio, self->sm, offset, NULL); + + // Build the state machine config. + pio_sm_config config = pio_get_default_sm_config(); + sm_config_set_clkdiv(&config, div); + config.execctrl = mp_obj_get_int_truncated(prog[PROG_EXECCTRL]); + config.shiftctrl = mp_obj_get_int_truncated(prog[PROG_SHIFTCTRL]); + + // Adjust wrap top/bottom to account for location of program in instruction memory. + config.execctrl += (offset << PIO_SM0_EXECCTRL_WRAP_TOP_LSB) + + (offset << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB); + + // Configure in pin base, if needed. + if (args[ARG_in_base].u_obj != mp_const_none) { + sm_config_set_in_pins(&config, mp_hal_get_pin_obj(args[ARG_in_base].u_obj)); + } + + // Configure out pins, if needed. + asm_pio_config_t out_config = ASM_PIO_CONFIG_DEFAULT; + asm_pio_get_pins("out", prog[PROG_OUT_PINS], args[ARG_out_base].u_obj, &out_config); + if (out_config.base >= 0) { + sm_config_set_out_pins(&config, out_config.base, out_config.count); + } + + // Configure set pin, if needed. + asm_pio_config_t set_config = ASM_PIO_CONFIG_DEFAULT; + asm_pio_get_pins("set", prog[PROG_SET_PINS], args[ARG_set_base].u_obj, &set_config); + if (set_config.base >= 0) { + sm_config_set_set_pins(&config, set_config.base, set_config.count); + } + + // Configure sideset pin, if needed. + asm_pio_config_t sideset_config = ASM_PIO_CONFIG_DEFAULT; + asm_pio_get_pins("sideset", prog[PROG_SIDESET_PINS], args[ARG_sideset_base].u_obj, &sideset_config); + if (sideset_config.base >= 0) { + uint32_t count = sideset_config.count; + if (config.execctrl & (1 << PIO_SM0_EXECCTRL_SIDE_EN_LSB)) { + // When sideset is optional, count includes the option bit. + ++count; + } + config.pinctrl |= count << PIO_SM0_PINCTRL_SIDESET_COUNT_LSB; + sm_config_set_sideset_pins(&config, sideset_config.base); + } + + // Override shift state if needed. + asm_pio_override_shiftctrl(args[ARG_in_shiftdir].u_obj, PIO_SM0_SHIFTCTRL_IN_SHIFTDIR_BITS, PIO_SM0_SHIFTCTRL_IN_SHIFTDIR_LSB, &config); + asm_pio_override_shiftctrl(args[ARG_out_shiftdir].u_obj, PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_BITS, PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_LSB, &config); + asm_pio_override_shiftctrl(args[ARG_push_thresh].u_obj, PIO_SM0_SHIFTCTRL_PUSH_THRESH_BITS, PIO_SM0_SHIFTCTRL_PUSH_THRESH_LSB, &config); + asm_pio_override_shiftctrl(args[ARG_pull_thresh].u_obj, PIO_SM0_SHIFTCTRL_PULL_THRESH_BITS, PIO_SM0_SHIFTCTRL_PULL_THRESH_LSB, &config); + + // Configure the state machine. + pio_sm_set_config(self->pio, self->sm, &config); + + // Configure the GPIO. + if (out_config.base >= 0) { + asm_pio_init_gpio(self->pio, self->sm, &out_config); + } + if (set_config.base >= 0) { + asm_pio_init_gpio(self->pio, self->sm, &set_config); + } + if (sideset_config.base >= 0) { + asm_pio_init_gpio(self->pio, self->sm, &sideset_config); + } + + return mp_const_none; +} + +// StateMachine(id, ...) +STATIC mp_obj_t rp2_state_machine_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + + // Get the StateMachine object. + mp_int_t sm_id = mp_obj_get_int(args[0]); + if (!(0 <= sm_id && sm_id < MP_ARRAY_SIZE(rp2_state_machine_obj))) { + mp_raise_ValueError("invalid StateMachine"); + } + const rp2_state_machine_obj_t *self = &rp2_state_machine_obj[sm_id]; + + if (n_args > 1 || n_kw > 0) { + // Configuration arguments given so init this StateMachine. + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + rp2_state_machine_init_helper(self, n_args - 1, args + 1, &kw_args); + } + + // Return the StateMachine object. + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t rp2_state_machine_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + return rp2_state_machine_init_helper(MP_OBJ_TO_PTR(args[0]), n_args - 1, args + 1, kw_args); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(rp2_state_machine_init_obj, 1, rp2_state_machine_init); + +// StateMachine.active([value]) +STATIC mp_obj_t rp2_state_machine_active(size_t n_args, const mp_obj_t *args) { + rp2_state_machine_obj_t *self = MP_OBJ_TO_PTR(args[0]); + if (n_args > 1) { + pio_sm_set_enabled(self->pio, self->sm, mp_obj_is_true(args[1])); + } + return mp_obj_new_bool((self->pio->ctrl >> self->sm) & 1); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(rp2_state_machine_active_obj, 1, 2, rp2_state_machine_active); + +// StateMachine.exec(instr) +STATIC mp_obj_t rp2_state_machine_exec(mp_obj_t self_in, mp_obj_t instr_in) { + rp2_state_machine_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_t rp2_module = mp_import_name(MP_QSTR_rp2, mp_const_none, MP_OBJ_NEW_SMALL_INT(0)); + mp_obj_t asm_pio_encode = mp_load_attr(rp2_module, MP_QSTR_asm_pio_encode); + uint32_t sideset_count = self->pio->sm[self->sm].pinctrl >> PIO_SM0_PINCTRL_SIDESET_COUNT_LSB; + mp_obj_t encoded_obj = mp_call_function_2(asm_pio_encode, instr_in, MP_OBJ_NEW_SMALL_INT(sideset_count)); + mp_int_t encoded = mp_obj_get_int(encoded_obj); + pio_sm_exec(self->pio, self->sm, encoded); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(rp2_state_machine_exec_obj, rp2_state_machine_exec); + +// StateMachine.get(buf=None, shift=0) +STATIC mp_obj_t rp2_state_machine_get(size_t n_args, const mp_obj_t *args) { + rp2_state_machine_obj_t *self = MP_OBJ_TO_PTR(args[0]); + mp_buffer_info_t bufinfo; + bufinfo.buf = NULL; + uint32_t shift = 0; + if (n_args > 1) { + if (args[1] != mp_const_none) { + mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_WRITE); + if (bufinfo.typecode == BYTEARRAY_TYPECODE) { + bufinfo.typecode = 'b'; + } else { + bufinfo.typecode |= 0x20; // make lowercase to support upper and lower + } + } + if (n_args > 2) { + shift = mp_obj_get_int(args[2]); + } + } + uint8_t *dest = bufinfo.buf; + const uint8_t *dest_top = dest + bufinfo.len; + for (;;) { + while (pio_sm_is_rx_fifo_empty(self->pio, self->sm)) { + // This delay must be fast. + mp_handle_pending(true); + MICROPY_HW_USBDEV_TASK_HOOK + } + uint32_t value = pio_sm_get(self->pio, self->sm) >> shift; + if (dest == NULL) { + return mp_obj_new_int_from_uint(value); + } + if (dest >= dest_top) { + return args[1]; + } + if (bufinfo.typecode == 'b') { + *(uint8_t *)dest = value; + dest += sizeof(uint8_t); + } else if (bufinfo.typecode == 'h') { + *(uint16_t *)dest = value; + dest += sizeof(uint16_t); + } else if (bufinfo.typecode == 'i') { + *(uint32_t *)dest = value; + dest += sizeof(uint32_t); + } else { + mp_raise_ValueError("unsupported buffer type"); + } + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(rp2_state_machine_get_obj, 1, 3, rp2_state_machine_get); + +// StateMachine.put(value, shift=0) +STATIC mp_obj_t rp2_state_machine_put(size_t n_args, const mp_obj_t *args) { + rp2_state_machine_obj_t *self = MP_OBJ_TO_PTR(args[0]); + uint32_t shift = 0; + if (n_args > 2) { + shift = mp_obj_get_int(args[2]); + } + uint32_t data; + mp_buffer_info_t bufinfo; + if (!mp_get_buffer(args[1], &bufinfo, MP_BUFFER_READ)) { + data = mp_obj_get_int_truncated(args[1]); + bufinfo.buf = &data; + bufinfo.len = sizeof(uint32_t); + bufinfo.typecode = 'I'; + } + const uint8_t *src = bufinfo.buf; + const uint8_t *src_top = src + bufinfo.len; + while (src < src_top) { + uint32_t value; + if (bufinfo.typecode == 'B' || bufinfo.typecode == BYTEARRAY_TYPECODE) { + value = *(uint8_t *)src; + src += sizeof(uint8_t); + } else if (bufinfo.typecode == 'H') { + value = *(uint16_t *)src; + src += sizeof(uint16_t); + } else if (bufinfo.typecode == 'I') { + value = *(uint32_t *)src; + src += sizeof(uint32_t); + } else { + mp_raise_ValueError("unsupported buffer type"); + } + while (pio_sm_is_tx_fifo_full(self->pio, self->sm)) { + // This delay must be fast. + mp_handle_pending(true); + MICROPY_HW_USBDEV_TASK_HOOK + } + pio_sm_put(self->pio, self->sm, value << shift); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(rp2_state_machine_put_obj, 2, 3, rp2_state_machine_put); + +// StateMachine.irq(handler=None, trigger=0|1, hard=False) +STATIC mp_obj_t rp2_state_machine_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_handler, ARG_trigger, ARG_hard }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_handler, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_trigger, MP_ARG_INT, {.u_int = 1} }, + { MP_QSTR_hard, MP_ARG_BOOL, {.u_bool = false} }, + }; + + // Parse the arguments. + rp2_state_machine_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Get the IRQ object. + rp2_state_machine_irq_obj_t *irq = MP_STATE_PORT(rp2_state_machine_irq_obj[self->id]); + + // Allocate the IRQ object if it doesn't already exist. + if (irq == NULL) { + irq = m_new_obj(rp2_state_machine_irq_obj_t); + irq->base.base.type = &mp_irq_type; + irq->base.methods = (mp_irq_methods_t *)&rp2_state_machine_irq_methods; + irq->base.parent = MP_OBJ_FROM_PTR(self); + irq->base.handler = mp_const_none; + irq->base.ishard = false; + MP_STATE_PORT(rp2_state_machine_irq_obj[self->id]) = irq; + } + + if (n_args > 1 || kw_args->used != 0) { + // Configure IRQ. + + // Disable all IRQs while data is updated. + irq_set_enabled(self->irq, false); + + // Update IRQ data. + irq->base.handler = args[ARG_handler].u_obj; + irq->base.ishard = args[ARG_hard].u_bool; + irq->flags = 0; + irq->trigger = args[ARG_trigger].u_int; + + // Enable IRQ if a handler is given. + if (args[ARG_handler].u_obj == mp_const_none) { + self->pio->inte0 &= ~(1 << (8 + self->sm)); + } else { + self->pio->inte0 |= 1 << (8 + self->sm); + } + + if (self->pio->inte0) { + irq_set_enabled(self->irq, true); + } + } + + return MP_OBJ_FROM_PTR(irq); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(rp2_state_machine_irq_obj, 1, rp2_state_machine_irq); + +STATIC const mp_rom_map_elem_t rp2_state_machine_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&rp2_state_machine_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&rp2_state_machine_active_obj) }, + { MP_ROM_QSTR(MP_QSTR_exec), MP_ROM_PTR(&rp2_state_machine_exec_obj) }, + { MP_ROM_QSTR(MP_QSTR_get), MP_ROM_PTR(&rp2_state_machine_get_obj) }, + { MP_ROM_QSTR(MP_QSTR_put), MP_ROM_PTR(&rp2_state_machine_put_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&rp2_state_machine_irq_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(rp2_state_machine_locals_dict, rp2_state_machine_locals_dict_table); + +const mp_obj_type_t rp2_state_machine_type = { + { &mp_type_type }, + .name = MP_QSTR_StateMachine, + .print = rp2_state_machine_print, + .make_new = rp2_state_machine_make_new, + .locals_dict = (mp_obj_dict_t *)&rp2_state_machine_locals_dict, +}; + +STATIC mp_uint_t rp2_state_machine_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { + rp2_state_machine_obj_t *self = MP_OBJ_TO_PTR(self_in); + rp2_state_machine_irq_obj_t *irq = MP_STATE_PORT(rp2_state_machine_irq_obj[PIO_NUM(self->pio)]); + irq_set_enabled(self->irq, false); + irq->flags = 0; + irq->trigger = new_trigger; + irq_set_enabled(self->irq, true); + return 0; +} + +STATIC mp_uint_t rp2_state_machine_irq_info(mp_obj_t self_in, mp_uint_t info_type) { + rp2_state_machine_obj_t *self = MP_OBJ_TO_PTR(self_in); + rp2_state_machine_irq_obj_t *irq = MP_STATE_PORT(rp2_state_machine_irq_obj[PIO_NUM(self->pio)]); + if (info_type == MP_IRQ_INFO_FLAGS) { + return irq->flags; + } else if (info_type == MP_IRQ_INFO_TRIGGERS) { + return irq->trigger; + } + return 0; +} + +STATIC const mp_irq_methods_t rp2_state_machine_irq_methods = { + .trigger = rp2_state_machine_irq_trigger, + .info = rp2_state_machine_irq_info, +}; diff --git a/ports/rp2/tusb_config.h b/ports/rp2/tusb_config.h new file mode 100644 index 0000000000..1402edf379 --- /dev/null +++ b/ports/rp2/tusb_config.h @@ -0,0 +1,34 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 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_RP2_TUSB_CONFIG_H +#define MICROPY_INCLUDED_RP2_TUSB_CONFIG_H + +#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE) + +#define CFG_TUD_CDC (1) +#define CFG_TUD_CDC_RX_BUFSIZE (256) +#define CFG_TUD_CDC_TX_BUFSIZE (256) + +#endif // MICROPY_INCLUDED_RP2_TUSB_CONFIG_H diff --git a/ports/rp2/tusb_port.c b/ports/rp2/tusb_port.c new file mode 100644 index 0000000000..afb566bdb4 --- /dev/null +++ b/ports/rp2/tusb_port.c @@ -0,0 +1,115 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 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 "tusb.h" + +#define USBD_VID (0x2E8A) // Raspberry Pi +#define USBD_PID (0x0005) // RP2 MicroPython + +#define USBD_DESC_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN) +#define USBD_MAX_POWER_MA (250) + +#define USBD_ITF_CDC (0) // needs 2 interfaces +#define USBD_ITF_MAX (2) + +#define USBD_CDC_EP_CMD (0x81) +#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) +#define USBD_STR_PRODUCT (0x02) +#define USBD_STR_SERIAL (0x03) +#define USBD_STR_CDC (0x04) + +// Note: descriptors returned from callbacks must exist long enough for transfer to complete + +static const tusb_desc_device_t usbd_desc_device = { + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .idVendor = USBD_VID, + .idProduct = USBD_PID, + .bcdDevice = 0x0100, + .iManufacturer = USBD_STR_MANUF, + .iProduct = USBD_STR_PRODUCT, + .iSerialNumber = USBD_STR_SERIAL, + .bNumConfigurations = 1, +}; + +static const uint8_t usbd_desc_cfg[USBD_DESC_LEN] = { + TUD_CONFIG_DESCRIPTOR(1, USBD_ITF_MAX, USBD_STR_0, 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, USBD_CDC_IN_OUT_MAX_SIZE), +}; + +static const char *const usbd_desc_str[] = { + [USBD_STR_MANUF] = "MicroPython", + [USBD_STR_PRODUCT] = "Board in FS mode", + [USBD_STR_SERIAL] = "000000000000", // TODO + [USBD_STR_CDC] = "Board CDC", +}; + +const uint8_t *tud_descriptor_device_cb(void) { + return (const uint8_t *)&usbd_desc_device; +} + +const uint8_t *tud_descriptor_configuration_cb(uint8_t index) { + (void)index; + return usbd_desc_cfg; +} + +const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { + #define DESC_STR_MAX (20) + static uint16_t desc_str[DESC_STR_MAX]; + + uint8_t len; + if (index == 0) { + desc_str[1] = 0x0409; // supported language is English + len = 1; + } else { + if (index >= sizeof(usbd_desc_str) / sizeof(usbd_desc_str[0])) { + return NULL; + } + const char *str = usbd_desc_str[index]; + for (len = 0; len < DESC_STR_MAX - 1 && str[len]; ++len) { + desc_str[1 + len] = str[len]; + } + } + + // first byte is length (including header), second byte is string type + desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * len + 2); + + return desc_str; +} diff --git a/ports/rp2/uart.c b/ports/rp2/uart.c new file mode 100644 index 0000000000..b7991563af --- /dev/null +++ b/ports/rp2/uart.c @@ -0,0 +1,63 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 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/runtime.h" +#include "py/ringbuf.h" +#include "py/mphal.h" +#include "uart.h" + +#include "hardware/uart.h" +#include "hardware/irq.h" +#include "hardware/regs/uart.h" + +#if MICROPY_HW_ENABLE_UART_REPL + +void uart_irq(void) { + uart_get_hw(uart_default)->icr = UART_UARTICR_BITS; // clear interrupt flags + if (uart_is_readable(uart_default)) { + int c = uart_getc(uart_default); + #if MICROPY_KBD_EXCEPTION + if (c == mp_interrupt_char) { + mp_keyboard_interrupt(); + return; + } + #endif + ringbuf_put(&stdin_ringbuf, c); + } +} + +void mp_uart_init(void) { + uart_get_hw(uart_default)->imsc = UART_UARTIMSC_BITS; // enable mask + uint irq_num = uart_get_index(uart_default) ? UART1_IRQ : UART0_IRQ; + irq_set_exclusive_handler(irq_num, uart_irq); + irq_set_enabled(irq_num, true); // enable irq +} + +void mp_uart_write_strn(const char *str, size_t len) { + uart_write_blocking(uart_default, (const uint8_t *)str, len); +} + +#endif diff --git a/ports/rp2/uart.h b/ports/rp2/uart.h new file mode 100644 index 0000000000..a49172f8f5 --- /dev/null +++ b/ports/rp2/uart.h @@ -0,0 +1,32 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 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_RP2_UART_H +#define MICROPY_INCLUDED_RP2_UART_H + +void mp_uart_init(void); +void mp_uart_write_strn(const char *str, size_t len); + +#endif // MICROPY_INCLUDED_RP2_UART_H From b8f4c623f9002099b0910ac873ef9e3acbcf428c Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 21 Jan 2021 19:24:12 +1100 Subject: [PATCH 305/337] github/workflows: Add CI workflow for rp2 port. Signed-off-by: Damien George --- .github/workflows/ports_rp2.yml | 23 +++++++++++++++++++++++ tools/ci.sh | 14 ++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 .github/workflows/ports_rp2.yml diff --git a/.github/workflows/ports_rp2.yml b/.github/workflows/ports_rp2.yml new file mode 100644 index 0000000000..668b79cae2 --- /dev/null +++ b/.github/workflows/ports_rp2.yml @@ -0,0 +1,23 @@ +name: rp2 port + +on: + push: + pull_request: + paths: + - '.github/workflows/*.yml' + - 'tools/**' + - 'py/**' + - 'extmod/**' + - 'lib/**' + - 'drivers/**' + - 'ports/rp2/**' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install packages + run: source tools/ci.sh && ci_rp2_setup + - name: Build + run: source tools/ci.sh && ci_rp2_build diff --git a/tools/ci.sh b/tools/ci.sh index 986ec6756e..4e4aad5600 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -176,6 +176,20 @@ function ci_qemu_arm_build { make ${MAKEOPTS} -C ports/qemu-arm -f Makefile.test test } +######################################################################################## +# ports/rp2 + +function ci_rp2_setup { + ci_gcc_arm_setup +} + +function ci_rp2_build { + make ${MAKEOPTS} -C mpy-cross + git submodule update --init lib/pico-sdk + git -C lib/pico-sdk submodule update --init lib/tinyusb + make ${MAKEOPTS} -C ports/rp2 +} + ######################################################################################## # ports/samd From 0f9a9129da0d0ef60956dcfa36b02b62d731b1b1 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 29 Jan 2021 17:45:33 +1100 Subject: [PATCH 306/337] stm32/rfcore: Fix flow control for IPCC RX IRQ. Don't clear the IPCC channel flag until we've actually handled the incoming data, or else the wireless firmware may clobber the IPCC buffer if more data arrives. This requires masking the IRQ until the data is handled. Signed-off-by: Jim Mussared --- ports/stm32/rfcore.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/ports/stm32/rfcore.c b/ports/stm32/rfcore.c index 7d0a8520a6..b11c4f2022 100644 --- a/ports/stm32/rfcore.c +++ b/ports/stm32/rfcore.c @@ -434,10 +434,16 @@ 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)) { + // Process new data. tl_process_msg(head, ch, parse); - // Clear receive channel. + // Clear receive channel (allows RF core to send more data to us). LL_C1_IPCC_ClearFlag_CHx(IPCC, ch); + + if (ch == IPCC_CH_BLE) { + // Renable IRQs for BLE now that we've cleared the flag. + LL_C1_IPCC_EnableReceiveChannel(IPCC, IPCC_CH_BLE); + } } } @@ -495,17 +501,17 @@ STATIC int tl_ble_wait_resp(void) { } } - // C2 set IPCC flag. + // C2 set IPCC flag -- process the data, clear the flag, and re-enable IRQs. 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) { + // Poll for completion rather than wait for IRQ->scheduler. 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); } /******************************************************************************/ @@ -632,7 +638,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_process_msg(&ipcc_mem_ble_evt_queue, IPCC_CH_BLE, &parse); + tl_check_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) { @@ -679,7 +685,8 @@ void IPCC_C1_RX_IRQHandler(void) { 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); + // Disable this IRQ until the incoming data is processed (in tl_check_msg). + LL_C1_IPCC_DisableReceiveChannel(IPCC, IPCC_CH_BLE); #if MICROPY_PY_BLUETOOTH // Queue up the scheduler to process UART data and run events. From 47d02b3104369430db9fd7a6f80f9fa47badaf65 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Fri, 29 Jan 2021 17:46:33 +1100 Subject: [PATCH 307/337] extmod/nimble: Improve the flow control for l2cap recv path. If the _IRQ_L2CAP_RECV handler does the actual consumption of the incoming data (i.e. via l2cap_recvinto), rather than setting a flag for non-scheduler-context to handle it later, then two things can happen: - It can starve the VM (i.e. the scheduled task never terminates). This is because calling l2cap_recvinto will empty the rx buffer, which will grant more credits to the channel (an HCI command), meaning more data can arrive. This means that the loop in hal_uart.c that keeps reading HCI data from the uart and executing NimBLE events as they are created will not terminate, preventing other VM code from running. - There's no flow control (i.e. data will arrive too quickly). The channel shouldn't be given credits until after we return from scheduler context. It's preferable that no work is done in scheduler/IRQ context. But to prevent this being a problem this commit changes l2cap_recvinto so that if it is called in IRQ context, and the Python handler empties the rx buffer, then don't grant credits until the Python handler is complete. Signed-off-by: Jim Mussared --- extmod/nimble/modbluetooth_nimble.c | 36 +++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index 8bead890d8..a2eb8a588c 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -1355,6 +1355,7 @@ typedef struct _mp_bluetooth_nimble_l2cap_channel_t { struct os_mbuf_pool sdu_mbuf_pool; struct os_mempool sdu_mempool; struct os_mbuf *rx_pending; + bool irq_in_progress; uint16_t mtu; os_membuf_t sdu_mem[]; } mp_bluetooth_nimble_l2cap_channel_t; @@ -1441,7 +1442,23 @@ STATIC int l2cap_channel_event(struct ble_l2cap_event *event, void *arg) { chan->chan->coc_rx.sdu = sdu_rx; ble_l2cap_get_chan_info(event->receive.chan, &info); + + // Don't allow granting more credits until after the IRQ is handled. + chan->irq_in_progress = true; + mp_bluetooth_gattc_on_l2cap_recv(event->receive.conn_handle, info.scid); + chan->irq_in_progress = false; + + // If all data has been consumed by the IRQ handler, then now allow + // more credits. If the IRQ handler doesn't consume all available data + // then rx_pending will be still set. + if (!chan->rx_pending) { + struct os_mbuf *sdu_rx = chan->chan->coc_rx.sdu; + assert(sdu_rx); + if (sdu_rx) { + ble_l2cap_recv_ready(chan->chan, sdu_rx); + } + } break; } case BLE_L2CAP_EVENT_COC_TX_UNSTALLED: { @@ -1508,6 +1525,7 @@ STATIC int create_l2cap_channel(uint16_t mtu, mp_bluetooth_nimble_l2cap_channel_ chan->mtu = mtu; chan->rx_pending = NULL; + chan->irq_in_progress = false; int err = os_mempool_init(&chan->sdu_mempool, buf_blocks, L2CAP_BUF_BLOCK_SIZE, chan->sdu_mem, "l2cap_sdu_pool"); if (err != 0) { @@ -1635,13 +1653,17 @@ int mp_bluetooth_l2cap_recvinto(uint16_t conn_handle, uint16_t cid, uint8_t *buf 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); + // If we're in the call stack of the l2cap_channel_event handler, then don't + // re-enable receiving yet (as we need to complete the rest of IRQ handler first). + if (!chan->irq_in_progress) { + // 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. From b9a35bebf75be53a817bf6341af14b882093e345 Mon Sep 17 00:00:00 2001 From: stijn Date: Sat, 16 Jan 2021 09:18:31 +0100 Subject: [PATCH 308/337] py/qstr.h: Remove QSTR_FROM_STR_STATIC macro. It practically does the same as qstr_from_str and was only used in one place, which should actually use the compile-time MP_QSTR_XXX form for consistency; qstr_from_str is for runtime strings only. --- ports/unix/main.c | 10 +++++----- py/qstr.h | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/ports/unix/main.c b/ports/unix/main.c index 6f85cbf8d0..af4328a4db 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -532,8 +532,8 @@ MP_NOINLINE int main_(int argc, char **argv) { { 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)); + mp_store_global(MP_QSTR_extra_coverage, MP_OBJ_FROM_PTR(&extra_coverage_obj)); + mp_store_global(MP_QSTR_extra_cpp_coverage, MP_OBJ_FROM_PTR(&extra_cpp_coverage_obj)); } #endif @@ -546,9 +546,9 @@ MP_NOINLINE int main_(int argc, char **argv) { // test_obj.attr = 42 // // mp_obj_t test_class_type, test_class_instance; - // test_class_type = mp_obj_new_type(QSTR_FROM_STR_STATIC("TestClass"), mp_const_empty_tuple, mp_obj_new_dict(0)); - // mp_store_name(QSTR_FROM_STR_STATIC("test_obj"), test_class_instance = mp_call_function_0(test_class_type)); - // mp_store_attr(test_class_instance, QSTR_FROM_STR_STATIC("attr"), mp_obj_new_int(42)); + // test_class_type = mp_obj_new_type(qstr_from_str("TestClass"), mp_const_empty_tuple, mp_obj_new_dict(0)); + // mp_store_name(qstr_from_str("test_obj"), test_class_instance = mp_call_function_0(test_class_type)); + // mp_store_attr(test_class_instance, qstr_from_str("attr"), mp_obj_new_int(42)); /* printf("bytes:\n"); diff --git a/py/qstr.h b/py/qstr.h index df87217ff3..0b6fb12b08 100644 --- a/py/qstr.h +++ b/py/qstr.h @@ -32,7 +32,7 @@ // See qstrdefs.h for a list of qstr's that are available as constants. // Reference them as MP_QSTR_xxxx. // -// Note: it would be possible to define MP_QSTR_xxx as qstr_from_str_static("xxx") +// Note: it would be possible to define MP_QSTR_xxx as qstr_from_str("xxx") // for qstrs that are referenced this way, but you don't want to have them in ROM. // first entry in enum will be MP_QSTRnull=0, which indicates invalid/no qstr @@ -55,7 +55,6 @@ typedef struct _qstr_pool_t { const byte *qstrs[]; } qstr_pool_t; -#define QSTR_FROM_STR_STATIC(s) (qstr_from_strn((s), strlen(s))) #define QSTR_TOTAL() (MP_STATE_VM(last_pool)->total_prev_len + MP_STATE_VM(last_pool)->len) void qstr_init(void); From fca2730ea01bc8457804884f4a0472ae0d938a19 Mon Sep 17 00:00:00 2001 From: stijn Date: Sat, 16 Jan 2021 09:24:22 +0100 Subject: [PATCH 309/337] lib/utils/pyexec: Remove obsolete LCD initialization. This was added a long time ago in 75abee206d1a575aa98a486d043c94d64df432c1 when USB host support was added to the stm (now stm32) port, and when this pyexec code was actually part of the stm port. It's unlikely to work as intended anymore. If it is needed in the future then generic hook macros can be added in pyexec. --- lib/utils/pyexec.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/utils/pyexec.c b/lib/utils/pyexec.c index 86321ac129..d1e955d65b 100644 --- a/lib/utils/pyexec.c +++ b/lib/utils/pyexec.c @@ -546,12 +546,6 @@ int pyexec_friendly_repl(void) { vstr_t line; vstr_init(&line, 32); - #if defined(USE_HOST_MODE) && MICROPY_HW_HAS_LCD - // in host mode, we enable the LCD for the repl - mp_obj_t lcd_o = mp_call_function_0(mp_load_name(qstr_from_str("LCD"))); - mp_call_function_1(mp_load_attr(lcd_o, qstr_from_str("light")), mp_const_true); - #endif - friendly_repl_reset: mp_hal_stdout_tx_str("MicroPython " MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE "; " MICROPY_HW_BOARD_NAME " with " MICROPY_HW_MCU_NAME "\r\n"); #if MICROPY_PY_BUILTINS_HELP From d48860c7ddfb37bdc48d0f3afc3a26dcd54cca0f Mon Sep 17 00:00:00 2001 From: stijn Date: Tue, 19 Jan 2021 13:07:51 +0100 Subject: [PATCH 310/337] tools/verifygitlog.py: Add script for verifying commit message format. The main rules enforced are: - At most 72 characters in the subject line, with a ": " in it. - At most 75 characters per line in the body. - No "noreply" email addresses. --- tools/verifygitlog.py | 122 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100755 tools/verifygitlog.py diff --git a/tools/verifygitlog.py b/tools/verifygitlog.py new file mode 100755 index 0000000000..0080b96bfa --- /dev/null +++ b/tools/verifygitlog.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 + +import re +import subprocess +import sys + +verbosity = 0 # Show what's going on, 0 1 or 2. +suggestions = 1 # Set to 0 to not include lengthy suggestions in error messages. + + +def verbose(*args): + if verbosity: + print(*args) + + +def very_verbose(*args): + if verbosity > 1: + print(*args) + + +def git_log(pretty_format, *args): + # Delete pretty argument from user args so it doesn't interfere with what we do. + args = ["git", "log"] + [arg for arg in args if "--pretty" not in args] + args.append("--pretty=format:" + pretty_format) + very_verbose("git_log", *args) + # Generator yielding each output line. + for line in subprocess.Popen(args, stdout=subprocess.PIPE).stdout: + yield line.decode().rstrip("\r\n") + + +def verify(sha): + verbose("verify", sha) + errors = [] + warnings = [] + + def error_text(err): + return "commit " + sha + ": " + err + + def error(err): + errors.append(error_text(err)) + + def warning(err): + warnings.append(error_text(err)) + + # Author and committer email. + for line in git_log("%ae%n%ce", sha, "-n1"): + very_verbose("email", line) + if "noreply" in line: + error("Unwanted email address: " + line) + + # Message body. + raw_body = list(git_log("%B", sha, "-n1")) + if not raw_body: + error("Message is empty") + return errors, warnings + + # Subject line. + subject_line = raw_body[0] + very_verbose("subject_line", subject_line) + if not re.match(r"^[^!]+: [A-Z]+.+ .+\.$", subject_line): + error("Subject line should contain ': ' and end in '.': " + subject_line) + if len(subject_line) >= 73: + error("Subject line should be 72 or less characters: " + subject_line) + + # Second one divides subject and body. + if len(raw_body) > 1 and raw_body[1]: + error("Second message line should be empty: " + raw_body[1]) + + # Message body lines. + for line in raw_body[2:]: + if len(line) >= 76: + error("Message lines should be 75 or less characters: " + line) + + if not raw_body[-1].startswith("Signed-off-by: ") or "@" not in raw_body[-1]: + warning("Message should be signed-off") + + return errors, warnings + + +def run(args): + verbose("run", *args) + has_errors = False + has_warnings = False + for sha in git_log("%h", *args): + errors, warnings = verify(sha) + has_errors |= any(errors) + has_warnings |= any(warnings) + for err in errors: + print("error:", err) + for err in warnings: + print("warning:", err) + if has_errors or has_warnings: + if suggestions: + print("See https://github.com/micropython/micropython/blob/master/CODECONVENTIONS.md") + else: + print("ok") + if has_errors: + sys.exit(1) + + +def show_help(): + print("usage: verifygitlog.py [-v -n -h] ...") + print("-v : increase verbosity, can be speficied multiple times") + print("-n : do not print multi-line suggestions") + print("-h : print this help message and exit") + print("... : arguments passed to git log to retrieve commits to verify") + print(" see https://www.git-scm.com/docs/git-log") + print(" passing no arguments at all will verify all commits") + print("examples:") + print("verifygitlog.py -n10 # Check last 10 commits") + print("verifygitlog.py -v master..HEAD # Check commits since master") + + +if __name__ == "__main__": + args = sys.argv[1:] + verbosity = args.count("-v") + suggestions = args.count("-n") == 0 + if "-h" in args: + show_help() + else: + args = [arg for arg in args if arg not in ["-v", "-n", "-h"]] + run(args) From 37c2f507a01af06627ff93eafa4fc80b2734620e Mon Sep 17 00:00:00 2001 From: stijn Date: Tue, 19 Jan 2021 15:06:50 +0100 Subject: [PATCH 311/337] github/workflows: Add workflow to verify commit message format. Using the new tools/verifygitlog.py script. --- .github/workflows/commit_formatting.yml | 14 ++++++++++++++ tools/ci.sh | 10 ++++++++++ 2 files changed, 24 insertions(+) create mode 100644 .github/workflows/commit_formatting.yml diff --git a/.github/workflows/commit_formatting.yml b/.github/workflows/commit_formatting.yml new file mode 100644 index 0000000000..5f96fbb934 --- /dev/null +++ b/.github/workflows/commit_formatting.yml @@ -0,0 +1,14 @@ +name: Check commit message formatting + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: '100' + - uses: actions/setup-python@v1 + - name: Check commit message formatting + run: source tools/ci.sh && ci_commit_formatting_run diff --git a/tools/ci.sh b/tools/ci.sh index 4e4aad5600..c6b641dae9 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -29,6 +29,16 @@ function ci_code_formatting_run { tools/codeformat.py -v } +######################################################################################## +# commit formatting + +function ci_commit_formatting_run { + git remote add upstream https://github.com/micropython/micropython.git + git fetch --depth=100 upstream master + # For a PR, upstream/master..HEAD ends with a merge commit into master, exlude that one. + tools/verifygitlog.py -v upstream/master..HEAD --no-merges +} + ######################################################################################## # code size From 499e199addacd3777244c61cc4c3b4efdfa9b300 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Tue, 19 Jan 2021 09:22:04 +0000 Subject: [PATCH 312/337] docs,stm32: Fix minor typos in RTC docs, and->an. --- docs/library/machine.RTC.rst | 2 +- docs/library/pyb.RTC.rst | 2 +- ports/stm32/rtc.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/library/machine.RTC.rst b/docs/library/machine.RTC.rst index d5a4f390b5..ae5446ef74 100644 --- a/docs/library/machine.RTC.rst +++ b/docs/library/machine.RTC.rst @@ -4,7 +4,7 @@ class RTC -- real time clock ============================ -The RTC is and independent clock that keeps track of the date +The RTC is an independent clock that keeps track of the date and time. Example usage:: diff --git a/docs/library/pyb.RTC.rst b/docs/library/pyb.RTC.rst index 4c736597cf..c477e7f035 100644 --- a/docs/library/pyb.RTC.rst +++ b/docs/library/pyb.RTC.rst @@ -4,7 +4,7 @@ class RTC -- real time clock ============================ -The RTC is and independent clock that keeps track of the date +The RTC is an independent clock that keeps track of the date and time. Example usage:: diff --git a/ports/stm32/rtc.c b/ports/stm32/rtc.c index bd898d4558..02b0f2dbd1 100644 --- a/ports/stm32/rtc.c +++ b/ports/stm32/rtc.c @@ -43,7 +43,7 @@ /// \moduleref pyb /// \class RTC - real time clock /// -/// The RTC is and independent clock that keeps track of the date +/// The RTC is an independent clock that keeps track of the date /// and time. /// /// Example usage: From 2aecf378be3190920fd0e50aff6ccdc83ecfd7e9 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Thu, 21 Jan 2021 10:09:02 +1100 Subject: [PATCH 313/337] tools/makemanifest.py: Add check that freeze path is a directory. Avoids accidentally writing freeze("path/to/file.py") and getting unexpected results. --- tools/makemanifest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/makemanifest.py b/tools/makemanifest.py index 9ef0368260..c07a3a6c77 100644 --- a/tools/makemanifest.py +++ b/tools/makemanifest.py @@ -172,6 +172,8 @@ def mkdir(filename): def freeze_internal(kind, path, script, opt): path = convert_path(path) + if not os.path.isdir(path): + raise FreezeError("freeze path must be a directory") if script is None and kind == KIND_AS_STR: if any(f[0] == KIND_AS_STR for f in manifest_list): raise FreezeError("can only freeze one str directory") From 993ab6aa2c2e8eadcfdf64371bfddaa8304f26cb Mon Sep 17 00:00:00 2001 From: Chris Hemingway Date: Sun, 24 Jan 2021 14:40:44 +0000 Subject: [PATCH 314/337] nrf/README: Add use of "make submodules" in alternative build paragraph. Add "make submodules" to commands when building for the first time. Otherwise, on a first time build, the submodules have not been checked out and a lot of `fatal error: nrfx.h: No such file or directory` errors are printed. --- ports/nrf/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ports/nrf/README.md b/ports/nrf/README.md index 22bb4af514..0341cd81cd 100644 --- a/ports/nrf/README.md +++ b/ports/nrf/README.md @@ -62,8 +62,9 @@ By default, the PCA10040 (nrf52832) is used as compile target. To build and flas Alternatively the target board could be defined: - make BOARD=pca10040 - make BOARD=pca10040 deploy + make submodules + make BOARD=pca10040 + make BOARD=pca10040 deploy ## Compile without LTO enabled From 5c37e76e4f27cf3553adb67947c0a8e0a5500591 Mon Sep 17 00:00:00 2001 From: Christopher Tse Date: Tue, 26 Jan 2021 03:28:20 -0600 Subject: [PATCH 315/337] esp8266/modules/neopixel.py: Add timing param to NeoPixel constructor. This matches the esp32 port. --- ports/esp8266/modules/neopixel.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ports/esp8266/modules/neopixel.py b/ports/esp8266/modules/neopixel.py index be56ed1b07..501a2689e7 100644 --- a/ports/esp8266/modules/neopixel.py +++ b/ports/esp8266/modules/neopixel.py @@ -7,12 +7,13 @@ from esp import neopixel_write class NeoPixel: ORDER = (1, 0, 2, 3) - def __init__(self, pin, n, bpp=3): + def __init__(self, pin, n, bpp=3, timing=1): self.pin = pin self.n = n self.bpp = bpp self.buf = bytearray(n * bpp) self.pin.init(pin.OUT) + self.timing = timing def __setitem__(self, index, val): offset = index * self.bpp @@ -28,4 +29,4 @@ class NeoPixel: self[i] = color def write(self): - neopixel_write(self.pin, self.buf, True) + neopixel_write(self.pin, self.buf, self.timing) From ddb53c9458381f71d896ebba60234716f1b156d9 Mon Sep 17 00:00:00 2001 From: Christopher Tse Date: Tue, 26 Jan 2021 22:52:55 -0600 Subject: [PATCH 316/337] docs/esp8266/quickref: Add warning block about NeoPixel timing. --- docs/esp8266/quickref.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/esp8266/quickref.rst b/docs/esp8266/quickref.rst index 1a22bd50a5..a478b66581 100644 --- a/docs/esp8266/quickref.rst +++ b/docs/esp8266/quickref.rst @@ -364,6 +364,13 @@ For low-level driving of a NeoPixel:: import esp esp.neopixel_write(pin, grb_buf, is800khz) +.. Warning:: + By default ``NeoPixel`` is configured to control the more popular *800kHz* + units. It is possible to use alternative timing to control other (typically + 400kHz) devices by passing ``timing=0`` when constructing the + ``NeoPixel`` object. + + APA102 driver ------------- From c2b5bfcc0c6648f549af215f3d2f47302e962c96 Mon Sep 17 00:00:00 2001 From: stijn Date: Wed, 27 Jan 2021 10:14:50 +0100 Subject: [PATCH 317/337] tools: Remove obsolete upip bootstrap script. The upip module is frozen into ports supporting it, and it is included in the source tree, so there is no need to get it from PyPi. Moreover the PyPi package referred to is an out-of-date version of upip which is basically unrelated to our upip.py because the source is taken from a fork of micropython-lib instead of this repository. --- tools/bootstrap_upip.sh | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100755 tools/bootstrap_upip.sh diff --git a/tools/bootstrap_upip.sh b/tools/bootstrap_upip.sh deleted file mode 100755 index 2891775d7d..0000000000 --- a/tools/bootstrap_upip.sh +++ /dev/null @@ -1,30 +0,0 @@ -# This script performs bootstrap installation of upip package manager from PyPI -# All the other packages can be installed using it. - -saved="$PWD" - -if [ "$1" = "" ]; then - dest=~/.micropython/lib/ -else - dest="$1" -fi - -if [ -z "$TMPDIR" ]; then - cd /tmp -else - cd $TMPDIR -fi - -# Remove any stale old version -rm -rf micropython-upip-* -wget -nd -rH -l1 -D files.pythonhosted.org https://pypi.org/project/micropython-upip/ --reject=html - -tar xfz micropython-upip-*.tar.gz -tmpd="$PWD" - -cd "$saved" -mkdir -p "$dest" -cp "$tmpd"/micropython-upip-*/upip*.py "$dest" - -echo "upip is installed. To use:" -echo "micropython -m upip --help" From cb8e2f02ab34b49e5bd42012f3bc9adf61607b25 Mon Sep 17 00:00:00 2001 From: stijn Date: Wed, 27 Jan 2021 15:44:53 +0100 Subject: [PATCH 318/337] py/gc: Fix debug printing of pointer. When DEBUG_printf is the standard printf, compilers require the value for %p to be an actual pointer instead of an integer. --- py/gc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/gc.c b/py/gc.c index 767f1b81c4..41bbaa1b57 100644 --- a/py/gc.c +++ b/py/gc.c @@ -294,7 +294,7 @@ STATIC void gc_sweep(void) { } #endif free_tail = 1; - DEBUG_printf("gc_sweep(%p)\n", PTR_FROM_BLOCK(block)); + DEBUG_printf("gc_sweep(%p)\n", (void *)PTR_FROM_BLOCK(block)); #if MICROPY_PY_GC_COLLECT_RETVAL MP_STATE_MEM(gc_collected)++; #endif From cb30928ac8da931f905d1c14468498d21108933a Mon Sep 17 00:00:00 2001 From: David CARLIER Date: Sat, 16 Jan 2021 20:48:19 +0000 Subject: [PATCH 319/337] py/persistentcode: Introduce MICROPY_PERSISTENT_CODE_SAVE_FILE option. This should be enabled when the mp_raw_code_save_file function is needed. It is enabled for mpy-cross, and a check for defined(__APPLE__) is added to cover Mac M1 systems. --- mpy-cross/mpconfigport.h | 8 ++++++++ py/mpconfig.h | 5 +++++ py/persistentcode.c | 9 ++------- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/mpy-cross/mpconfigport.h b/mpy-cross/mpconfigport.h index 21d3e12ed4..7ff3d1b6b4 100644 --- a/mpy-cross/mpconfigport.h +++ b/mpy-cross/mpconfigport.h @@ -30,6 +30,14 @@ #define MICROPY_PERSISTENT_CODE_LOAD (0) #define MICROPY_PERSISTENT_CODE_SAVE (1) +#ifndef MICROPY_PERSISTENT_CODE_SAVE_FILE +#if defined(__i386__) || defined(__x86_64__) || defined(_WIN32) || defined(__unix__) || defined(__APPLE__) +#define MICROPY_PERSISTENT_CODE_SAVE_FILE (1) +#else +#define MICROPY_PERSISTENT_CODE_SAVE_FILE (0) +#endif +#endif + #define MICROPY_EMIT_X64 (1) #define MICROPY_EMIT_X86 (1) #define MICROPY_EMIT_THUMB (1) diff --git a/py/mpconfig.h b/py/mpconfig.h index 1df2e8fe91..518bddd6c8 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -283,6 +283,11 @@ #define MICROPY_PERSISTENT_CODE_SAVE (0) #endif +// Whether to support saving persistent code to a file via mp_raw_code_save_file +#ifndef MICROPY_PERSISTENT_CODE_SAVE_FILE +#define MICROPY_PERSISTENT_CODE_SAVE_FILE (0) +#endif + // Whether generated code can persist independently of the VM/runtime instance // This is enabled automatically when needed by other features #ifndef MICROPY_PERSISTENT_CODE diff --git a/py/persistentcode.c b/py/persistentcode.c index da3234a5fe..084632a60f 100644 --- a/py/persistentcode.c +++ b/py/persistentcode.c @@ -825,10 +825,7 @@ void mp_raw_code_save(mp_raw_code_t *rc, mp_print_t *print) { save_raw_code(print, rc, &qw); } -// here we define mp_raw_code_save_file depending on the port -// TODO abstract this away properly - -#if defined(__i386__) || defined(__x86_64__) || defined(_WIN32) || defined(__unix__) +#if MICROPY_PERSISTENT_CODE_SAVE_FILE #include #include @@ -853,8 +850,6 @@ void mp_raw_code_save_file(mp_raw_code_t *rc, const char *filename) { MP_THREAD_GIL_ENTER(); } -#else -#error mp_raw_code_save_file not implemented for this platform -#endif +#endif // MICROPY_PERSISTENT_CODE_SAVE_FILE #endif // MICROPY_PERSISTENT_CODE_SAVE From 407df82f813d7a8d522b5b43e39f91816dea26a6 Mon Sep 17 00:00:00 2001 From: Samuelson <59143578+TheNameIsSamSamuelson@users.noreply.github.com> Date: Sat, 9 Jan 2021 11:33:07 -0300 Subject: [PATCH 320/337] docs/develop/natmod: Fix a small typo, con->can. --- docs/develop/natmod.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/develop/natmod.rst b/docs/develop/natmod.rst index a4c188719a..8ffe49591c 100644 --- a/docs/develop/natmod.rst +++ b/docs/develop/natmod.rst @@ -202,7 +202,7 @@ Module usage in MicroPython Once the module is built there should be a file called ``factorial.mpy``. Copy this so it is accessible on the filesystem of your MicroPython system and can be -found in the import path. The module con now be accessed in Python just like any +found in the import path. The module can now be accessed in Python just like any other module, for example:: import factorial From ef9fde7339b12e17596966afb2b3d641ca5fcff2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 31 Jan 2021 23:17:42 +1100 Subject: [PATCH 321/337] LICENSE,docs: Update copyright year range to include 2021. Signed-off-by: Damien George --- LICENSE | 2 +- docs/conf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 8c5e4fe4c3..3193eb8cec 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013-2020 Damien P. George +Copyright (c) 2013-2021 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 diff --git a/docs/conf.py b/docs/conf.py index 460886b4da..4ca4d18f50 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -66,7 +66,7 @@ master_doc = 'index' # General information about the project. project = 'MicroPython' -copyright = '2014-2020, Damien P. George, Paul Sokolovsky, and contributors' +copyright = '2014-2021, Damien P. George, Paul Sokolovsky, and contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the From 4fb5f012c32a2ed394091039a7213472001a98c9 Mon Sep 17 00:00:00 2001 From: iTitou Date: Wed, 20 Jan 2021 10:33:40 +0100 Subject: [PATCH 322/337] py/makeversionhdr: Honor SOURCE_DATE_EPOCH if present. This environment variable, if defined during the build process, indicates a fixed time that should be used in place of "now" when such a time is explicitely referenced. This allows for reproducible builds of micropython. See https://reproducible-builds.org/specs/source-date-epoch/ Signed-off-by: iTitou --- py/makeversionhdr.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/py/makeversionhdr.py b/py/makeversionhdr.py index 970a86e6ca..2f4bc91826 100644 --- a/py/makeversionhdr.py +++ b/py/makeversionhdr.py @@ -80,6 +80,12 @@ def make_version_header(filename): git_tag, git_hash = info + build_date = datetime.date.today() + if "SOURCE_DATE_EPOCH" in os.environ: + build_date = datetime.datetime.utcfromtimestamp( + int(os.environ["SOURCE_DATE_EPOCH"]) + ).date() + # Generate the file with the git and version info file_data = """\ // This file was generated by py/makeversionhdr.py @@ -89,7 +95,7 @@ def make_version_header(filename): """ % ( git_tag, git_hash, - datetime.date.today().strftime("%Y-%m-%d"), + build_date.strftime("%Y-%m-%d"), ) # Check if the file contents changed from last time From b8f5f5cd85e83fd136fac5e90ed5d486c2924a42 Mon Sep 17 00:00:00 2001 From: iTitou Date: Wed, 20 Jan 2021 12:13:04 +0100 Subject: [PATCH 323/337] github/workflows/ports_unix.yml: Add job for a reproducible build. With a check for reproducible build date. Invocation of the test suite is not needed because it's already run in another job. Signed-off-by: iTitou --- .github/workflows/ports_unix.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index 552fd72d84..eb1416045c 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -26,6 +26,17 @@ jobs: if: failure() run: tests/run-tests --print-failures + reproducible: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build with reproducible date + run: source tools/ci.sh && ci_unix_minimal_build + env: + SOURCE_DATE_EPOCH: 1234567890 + - name: Check reproducible build date + run: echo | ports/unix/micropython-minimal -i | grep 'on 2009-02-13;' + standard: runs-on: ubuntu-latest steps: From 35a6f6231e482936b7ef7e8fe3dfc790a7beb4ed Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 1 Feb 2021 18:44:28 +1100 Subject: [PATCH 324/337] tests/extmod/utime_time_ns.py: Relax bounds on time_ns measurement. Some devices have lower precision than 1ms for time_ns() (eg PYBv1.x has 3.9ms resolution of the RTC) so make the test more lenient for them. Signed-off-by: Damien George --- tests/extmod/utime_time_ns.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/extmod/utime_time_ns.py b/tests/extmod/utime_time_ns.py index 8f3890f1cb..0d13f839d4 100644 --- a/tests/extmod/utime_time_ns.py +++ b/tests/extmod/utime_time_ns.py @@ -11,14 +11,14 @@ except (ImportError, AttributeError): t0 = utime.time_ns() -utime.sleep_us(1000) +utime.sleep_us(5000) t1 = utime.time_ns() # Check that time_ns increases. print(t0 < t1) -# Check that time_ns counts correctly, but be very lenient with the upper bound (50ms). -if 950000 < t1 - t0 < 50000000: +# Check that time_ns counts correctly, but be very lenient with the bounds (2ms to 50ms). +if 2000000 < t1 - t0 < 50000000: print(True) else: print(t0, t1, t1 - t0) From ffded488109230dc488b295e19eff7a0055ed0fa Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 1 Feb 2021 22:30:50 +1100 Subject: [PATCH 325/337] zephyr/machine_uart: Fix arg of machine_uart_ioctl to make it uintptr_t. Signed-off-by: Damien George --- ports/zephyr/machine_uart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/zephyr/machine_uart.c b/ports/zephyr/machine_uart.c index 6ae8707d7f..23d7d59443 100644 --- a/ports/zephyr/machine_uart.c +++ b/ports/zephyr/machine_uart.c @@ -131,7 +131,7 @@ STATIC mp_uint_t machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_uin return size; } -STATIC mp_uint_t machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, mp_uint_t arg, int *errcode) { +STATIC mp_uint_t machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { mp_uint_t ret; if (request == MP_STREAM_POLL) { From c9210a65dfce0771186916a1d43cbf63133773ba Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Mon, 1 Feb 2021 18:21:12 +0000 Subject: [PATCH 326/337] rp2/machine_pin: Change N_GPIOS to NUM_BANK0_GPIOS for pico-sdk compat. This fixes machine_pin.c to build against the new pico-sdk coming down the pipeline, whilst still working with the existing version. --- ports/rp2/machine_pin.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/rp2/machine_pin.c b/ports/rp2/machine_pin.c index f798c10fdf..0525e0f9f6 100644 --- a/ports/rp2/machine_pin.c +++ b/ports/rp2/machine_pin.c @@ -71,7 +71,7 @@ typedef struct _machine_pin_irq_obj_t { STATIC const mp_irq_methods_t machine_pin_irq_methods; -STATIC const machine_pin_obj_t machine_pin_obj[N_GPIOS] = { +STATIC const machine_pin_obj_t machine_pin_obj[NUM_BANK0_GPIOS] = { {{&machine_pin_type}, 0}, {{&machine_pin_type}, 1}, {{&machine_pin_type}, 2}, @@ -134,7 +134,7 @@ void machine_pin_init(void) { } void machine_pin_deinit(void) { - for (int i = 0; i < N_GPIOS; ++i) { + for (int i = 0; i < NUM_BANK0_GPIOS; ++i) { gpio_set_irq_enabled(i, GPIO_IRQ_ALL, false); } irq_set_enabled(IO_IRQ_BANK0, false); From 52d3ae707d7ddb8cf2c06962923226221d281206 Mon Sep 17 00:00:00 2001 From: graham sanderson Date: Mon, 1 Feb 2021 15:48:03 -0600 Subject: [PATCH 327/337] rp2/memmap_mp.ld: Update for latest SDK. --- ports/rp2/memmap_mp.ld | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ports/rp2/memmap_mp.ld b/ports/rp2/memmap_mp.ld index 5cebd33466..0dc96743ea 100644 --- a/ports/rp2/memmap_mp.ld +++ b/ports/rp2/memmap_mp.ld @@ -35,7 +35,7 @@ SECTIONS { /* Second stage bootloader is prepended to the image. It must be 256 bytes big and checksummed. It is usually built by the boot_stage2 target - in the Pico SDK + in the Raspberry Pi Pico SDK */ .flash_begin : { @@ -59,12 +59,11 @@ SECTIONS */ .text : { - __reset_start = .; - KEEP (*(.reset)) - . = ALIGN(256); - __reset_end = .; - ASSERT(__reset_end - __reset_start == 256, "ERROR: reset section should only be 256 bytes"); + __logical_binary_start = .; KEEP (*(.vectors)) + KEEP (*(.binary_info_header)) + __binary_info_header_end = .; + KEEP (*(.reset)) /* TODO revisit this now memset/memcpy/float in ROM */ /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from * FLASH ... we will include any thing excluded here in .data below by default */ @@ -247,6 +246,8 @@ SECTIONS /* Check if data + heap + stack exceeds RAM limit */ ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed") + + ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary") /* todo assert on extra code */ } From 9b277d981528173f59daab196873a8c11aa17e26 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 2 Feb 2021 10:56:45 +1100 Subject: [PATCH 328/337] lib/pico-sdk: Update to latest version v1.0.1. In particular it fixes GPIO19 so that it can be used as an output. Signed-off-by: Damien George --- lib/pico-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pico-sdk b/lib/pico-sdk index 493ed000dd..2d5789eca8 160000 --- a/lib/pico-sdk +++ b/lib/pico-sdk @@ -1 +1 @@ -Subproject commit 493ed000ddfc21aa1db933f5477dba52ac841aac +Subproject commit 2d5789eca89658a7f0a01e2d6010c0f254605d72 From 7a9027fd5d64868322b9a82b0ac47ac0eba0167d Mon Sep 17 00:00:00 2001 From: Tim Radvan Date: Sat, 30 Jan 2021 21:48:53 +0000 Subject: [PATCH 329/337] rp2/rp2_pio: Add JMP PIN support for PIO. PIO state machines can make a conditional jump on the state of a pin: the `JMP PIN` command. This requires the pin to be configured with `sm_config_set_jmp_pin`, but until now we didn't have a way of doing that in MicroPython. This commit adds a new `jmp_pin=None` argument to `StateMachine`. If it is not `None` then we try to interpret it as a Pin, and pass its value to `sm_config_set_jmp_pin`. Signed-off-by: Tim Radvan --- ports/rp2/rp2_pio.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ports/rp2/rp2_pio.c b/ports/rp2/rp2_pio.c index c8542127c0..7d8921d0c3 100644 --- a/ports/rp2/rp2_pio.c +++ b/ports/rp2/rp2_pio.c @@ -422,13 +422,14 @@ STATIC void rp2_state_machine_print(const mp_print_t *print, mp_obj_t self_in, m } // StateMachine.init(prog, freq=-1, *, -// in_base=None, out_base=None, set_base=None, sideset_base=None, -// in_shiftdir=None, out_shiftdir=None, push_thresh=None, pull_thresh=None, +// in_base=None, out_base=None, set_base=None, jmp_pin=None, +// sideset_base=None, in_shiftdir=None, out_shiftdir=None, +// push_thresh=None, pull_thresh=None, // ) STATIC mp_obj_t rp2_state_machine_init_helper(const rp2_state_machine_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_prog, ARG_freq, - ARG_in_base, ARG_out_base, ARG_set_base, ARG_sideset_base, + ARG_in_base, ARG_out_base, ARG_set_base, ARG_jmp_pin, ARG_sideset_base, ARG_in_shiftdir, ARG_out_shiftdir, ARG_push_thresh, ARG_pull_thresh }; static const mp_arg_t allowed_args[] = { @@ -437,6 +438,7 @@ STATIC mp_obj_t rp2_state_machine_init_helper(const rp2_state_machine_obj_t *sel { MP_QSTR_in_base, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_out_base, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_set_base, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_jmp_pin, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_sideset_base, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_in_shiftdir, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_out_shiftdir, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, @@ -501,6 +503,11 @@ STATIC mp_obj_t rp2_state_machine_init_helper(const rp2_state_machine_obj_t *sel sm_config_set_set_pins(&config, set_config.base, set_config.count); } + // Configure jmp pin, if needed. + if (args[ARG_jmp_pin].u_obj != mp_const_none) { + sm_config_set_jmp_pin(&config, mp_hal_get_pin_obj(args[ARG_jmp_pin].u_obj)); + } + // Configure sideset pin, if needed. asm_pio_config_t sideset_config = ASM_PIO_CONFIG_DEFAULT; asm_pio_get_pins("sideset", prog[PROG_SIDESET_PINS], args[ARG_sideset_base].u_obj, &sideset_config); From 3ea05e499d52beaecf4c9c54a67510ac031fe27b Mon Sep 17 00:00:00 2001 From: Tim Radvan Date: Mon, 1 Feb 2021 20:07:19 +0000 Subject: [PATCH 330/337] examples/rp2: Add pio_uart_rx.py example. This was adapted from the `pio/uart_rx` example from the `pico-examples` repository: https://github.com/raspberrypi/pico-examples/blob/master/pio/uart_rx/uart_rx.pio It demonstrates the `jmp_pin` feature in action. Signed-off-by: Tim Radvan --- examples/rp2/pio_uart_rx.py | 104 ++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 examples/rp2/pio_uart_rx.py diff --git a/examples/rp2/pio_uart_rx.py b/examples/rp2/pio_uart_rx.py new file mode 100644 index 0000000000..41dfde3dad --- /dev/null +++ b/examples/rp2/pio_uart_rx.py @@ -0,0 +1,104 @@ +# Example using PIO to create a UART RX interface. +# +# To make it work you'll need a wire connecting GPIO4 and GPIO3. +# +# Demonstrates: +# - PIO shifting in data on a pin +# - PIO jmp(pin) instruction +# - PIO irq handler +# - using the second core via _thread + +import _thread +from machine import Pin, UART +from rp2 import PIO, StateMachine, asm_pio + +UART_BAUD = 9600 + +HARD_UART_TX_PIN = Pin(4, Pin.OUT) +PIO_RX_PIN = Pin(3, Pin.IN, Pin.PULL_UP) + + +@asm_pio( + autopush=True, + push_thresh=8, + in_shiftdir=rp2.PIO.SHIFT_RIGHT, +) +def uart_rx_mini(): + # fmt: off + # Wait for start bit + wait(0, pin, 0) + # Preload bit counter, delay until eye of first data bit + set(x, 7) [10] + # Loop 8 times + label("bitloop") + # Sample data + in_(pins, 1) + # Each iteration is 8 cycles + jmp(x_dec, "bitloop") [6] + # fmt: on + + +@asm_pio( + in_shiftdir=rp2.PIO.SHIFT_RIGHT, +) +def uart_rx(): + # fmt: off + label("start") + # Stall until start bit is asserted + wait(0, pin, 0) + # Preload bit counter, then delay until halfway through + # the first data bit (12 cycles incl wait, set). + set(x, 7) [10] + label("bitloop") + # Shift data bit into ISR + in_(pins, 1) + # Loop 8 times, each loop iteration is 8 cycles + jmp(x_dec, "bitloop") [6] + # Check stop bit (should be high) + jmp(pin, "good_stop") + # Either a framing error or a break. Set a sticky flag + # and wait for line to return to idle state. + irq(block, 4) + wait(1, pin, 0) + # Don't push data if we didn't see good framing. + jmp("start") + # No delay before returning to start; a little slack is + # important in case the TX clock is slightly too fast. + label("good_stop") + push(block) + # fmt: on + + +# The handler for a UART break detected by the PIO. +def handler(sm): + print("break", time.ticks_ms(), end=" ") + + +# Function for core1 to execute to write to the given UART. +def core1_task(uart, text): + uart.write(text) + + +# Set up the hard UART we're going to use to print characters. +uart = UART(1, UART_BAUD, tx=HARD_UART_TX_PIN) + +for pio_prog in ("uart_rx_mini", "uart_rx"): + # Set up the state machine we're going to use to receive the characters. + sm = StateMachine( + 0, + globals()[pio_prog], + freq=8 * UART_BAUD, + in_base=PIO_RX_PIN, # For WAIT, IN + jmp_pin=PIO_RX_PIN, # For JMP + ) + sm.irq(handler) + sm.active(1) + + # Tell core 1 to print some text to UART 1 + text = "Hello, world from PIO, using {}!".format(pio_prog) + _thread.start_new_thread(core1_task, (uart, text)) + + # Echo characters received from PIO to the console. + for i in range(len(text)): + print(chr(sm.get() >> 24), end="") + print() From 7be1f7790278d2c40e7a828e340c43f257c7d748 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 2 Feb 2021 12:09:33 +1100 Subject: [PATCH 331/337] stm32/usbd_cdc_interface: Don't wait in usbd_cdc_tx_always if suspended. MCUs with device-only USB peripherals (eg L0, WB) do not implement (at least not in the ST HAL) the HAL_PCD_DisconnectCallback event. So if a USB cable is disconnected the USB driver does not deinitialise itself (usbd_cdc_deinit is not called) and the CDC driver can stay in the USBD_CDC_CONNECT_STATE_CONNECTED state. Then if the USB was attached to the REPL, output can become very slow waiting in usbd_cdc_tx_always for 500ms for each character. The disconnect event is not implemented on these MCUs but the suspend event is. And in the situation where the USB cable is disconnected the suspend event is raised because SOF packets are no longer received. The issue of very slow output on these MCUs is fixed in this commit (really worked around) by adding a check in usbd_cdc_tx_always to see if the USB device state is suspended, and, if so, breaking out of the 500ms wait loop. This should also help all MCUs for a real USB suspend. A proper fix for MCUs with device-only USB would be to implement or somehow synthesise the HAL_PCD_DisconnectCallback event. See issue #6672. Signed-off-by: Damien George --- ports/stm32/usbd_cdc_interface.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/stm32/usbd_cdc_interface.c b/ports/stm32/usbd_cdc_interface.c index a8261a958a..a465f608a6 100644 --- a/ports/stm32/usbd_cdc_interface.c +++ b/ports/stm32/usbd_cdc_interface.c @@ -382,6 +382,10 @@ void usbd_cdc_tx_always(usbd_cdc_itf_t *cdc, const uint8_t *buf, uint32_t len) { uint32_t start = HAL_GetTick(); while (usbd_cdc_tx_buffer_full(cdc) && HAL_GetTick() - start <= 500) { usbd_cdc_try_tx(cdc); + if (cdc->base.usbd->pdev->dev_state == USBD_STATE_SUSPENDED) { + // The USB is suspended so buffer will never be drained; exit loop + break; + } if (query_irq() == IRQ_STATE_DISABLED) { // IRQs disabled so buffer will never be drained; exit loop break; From 5ef71cd16793ab254a1f0e7dd46bb9f0c5809f44 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Tue, 2 Feb 2021 15:09:15 +1100 Subject: [PATCH 332/337] stm32/mboot: Change debug compiler optimisation from -O0 to -Og. With mboot encrpytion and fsload enabled, the DEBUG build -O0 compiler settings result in mboot no longer fitting in the 32k sector. This commit changes this to -Og which also brings it into line with the regular stm32 build. --- 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 ad2fda3eee..0c92442597 100755 --- a/ports/stm32/mboot/Makefile +++ b/ports/stm32/mboot/Makefile @@ -92,7 +92,7 @@ LDFLAGS += --gc-sections # Debugging/Optimization ifeq ($(DEBUG), 1) CFLAGS += -g -DPENDSV_DEBUG -COPT = -O0 +COPT = -Og else COPT += -Os -DNDEBUG endif From 03974485016654de09e42737ea875bc601d5ec56 Mon Sep 17 00:00:00 2001 From: stijn Date: Thu, 14 Jan 2021 13:23:15 +0100 Subject: [PATCH 333/337] tests/run-tests: Change default Python command used on Windows. Default to just calling python since that is most commonly available: the official installer or zipfiles from python.org, anaconda, nupkg all result in python being available but not python3. In other words: the default used so far is wrong. Note that os.name is 'posix' when running the python version which comes with Cygwin or MSys2 so they are not affected by this. However of all possible ways to get Python on Windows, only Cygwin provides no python command so update the default way for running tests in the README. --- ports/windows/README.md | 4 ++++ tests/run-tests | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ports/windows/README.md b/ports/windows/README.md index 8d907d1b72..1b904f8f5e 100644 --- a/ports/windows/README.md +++ b/ports/windows/README.md @@ -90,6 +90,10 @@ Running the tests This is similar for all ports: cd ../../tests + python ./run-tests + +Though when running on Cygwin and using Cygwin's Python installation you'll need: + python3 ./run-tests Depending on the combination of platform and Python version used it might be diff --git a/tests/run-tests b/tests/run-tests index cb5b5cd0a5..b733d8a9fc 100755 --- a/tests/run-tests +++ b/tests/run-tests @@ -20,7 +20,7 @@ def base_path(*p): # is of lower version, you can point MICROPY_CPYTHON3 environment var # to the correct executable. if os.name == 'nt': - CPYTHON3 = os.getenv('MICROPY_CPYTHON3', 'python3.exe') + CPYTHON3 = os.getenv('MICROPY_CPYTHON3', 'python') MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', base_path('../ports/windows/micropython.exe')) else: CPYTHON3 = os.getenv('MICROPY_CPYTHON3', 'python3') From 81a4d96aed7221ea497bb90f135b3fd14ab22c0c Mon Sep 17 00:00:00 2001 From: stijn Date: Sat, 16 Jan 2021 09:01:17 +0100 Subject: [PATCH 334/337] windows/msvc: Use same default python command as core. --- ports/windows/msvc/genhdr.targets | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/windows/msvc/genhdr.targets b/ports/windows/msvc/genhdr.targets index 3af0ea2636..9ea6ed28cb 100644 --- a/ports/windows/msvc/genhdr.targets +++ b/ports/windows/msvc/genhdr.targets @@ -14,6 +14,7 @@ $(PySrcDir)qstrdefs.h $(DestDir)qstrdefscollected.h $(DestDir)qstrdefs.generated.h + $(MICROPY_CPYTHON3) python cl.exe $([System.IO.Path]::Combine(`$(CLToolPath)`, `$(CLToolExe)`)) From 195e7dfa0681bcfbc67cfaa68f863c51dcac95da Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 2 Feb 2021 12:55:11 +1100 Subject: [PATCH 335/337] rp2/modmachine: Implement additional functions incl unique_id and idle. Added functions in the machine module are: - unique_id (returns 8 bytes) - soft_reset - idle - lightsleep, deepsleep (not power saving at the moment) - disable_irq, enable_irq - time_pulse_us Signed-off-by: Damien George --- ports/rp2/CMakeLists.txt | 1 + ports/rp2/main.c | 11 +++++-- ports/rp2/modmachine.c | 65 ++++++++++++++++++++++++++++++++++++++++ ports/rp2/mpconfigport.h | 1 + 4 files changed, 76 insertions(+), 2 deletions(-) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 400cb8480e..160456c6b1 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -143,6 +143,7 @@ target_link_libraries(${MICROPYTHON_TARGET} pico_multicore pico_stdlib_headers pico_stdlib + pico_unique_id tinyusb_device ) diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 3e956ae78c..64218f97c5 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -110,9 +110,15 @@ int main(int argc, char **argv) { pyexec_frozen_module("_boot.py"); // Execute user scripts. - pyexec_file_if_exists("boot.py"); + int ret = pyexec_file_if_exists("boot.py"); + if (ret & PYEXEC_FORCED_EXIT) { + goto soft_reset_exit; + } if (pyexec_mode_kind == PYEXEC_MODE_FRIENDLY_REPL) { - pyexec_file_if_exists("main.py"); + ret = pyexec_file_if_exists("main.py"); + if (ret & PYEXEC_FORCED_EXIT) { + goto soft_reset_exit; + } } for (;;) { @@ -127,6 +133,7 @@ int main(int argc, char **argv) { } } + soft_reset_exit: mp_printf(MP_PYTHON_PRINTER, "MPY: soft reboot\n"); rp2_pio_deinit(); machine_pin_deinit(); diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 228e9543c1..dbaafabe86 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -26,18 +26,34 @@ #include "py/runtime.h" #include "py/mphal.h" +#include "lib/utils/pyexec.h" #include "extmod/machine_i2c.h" #include "extmod/machine_mem.h" +#include "extmod/machine_pulse.h" #include "extmod/machine_spi.h" #include "modmachine.h" #include "hardware/clocks.h" #include "hardware/watchdog.h" #include "pico/bootrom.h" +#include "pico/unique_id.h" #define RP2_RESET_PWRON (1) #define RP2_RESET_WDT (3) +STATIC mp_obj_t machine_unique_id(void) { + pico_unique_board_id_t id; + pico_get_unique_board_id(&id); + return mp_obj_new_bytes(id.id, sizeof(id.id)); +} +MP_DEFINE_CONST_FUN_OBJ_0(machine_unique_id_obj, machine_unique_id); + +STATIC mp_obj_t machine_soft_reset(void) { + pyexec_system_exit = PYEXEC_FORCED_EXIT; + mp_raise_type(&mp_type_SystemExit); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_soft_reset_obj, machine_soft_reset); + STATIC mp_obj_t machine_reset(void) { watchdog_reboot(0, SRAM_END, 0); for (;;) { @@ -69,12 +85,61 @@ STATIC mp_obj_t machine_freq(void) { } MP_DEFINE_CONST_FUN_OBJ_0(machine_freq_obj, machine_freq); +STATIC mp_obj_t machine_idle(void) { + best_effort_wfe_or_timeout(make_timeout_time_ms(1)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_idle_obj, machine_idle); + +STATIC mp_obj_t machine_lightsleep(size_t n_args, const mp_obj_t *args) { + if (n_args == 0) { + for (;;) { + MICROPY_EVENT_POLL_HOOK + } + } else { + mp_hal_delay_ms(mp_obj_get_int(args[0])); + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_lightsleep_obj, 0, 1, machine_lightsleep); + +STATIC mp_obj_t machine_deepsleep(size_t n_args, const mp_obj_t *args) { + machine_lightsleep(n_args, args); + return machine_reset(); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_deepsleep_obj, 0, 1, machine_deepsleep); + +STATIC mp_obj_t machine_disable_irq(void) { + uint32_t state = MICROPY_BEGIN_ATOMIC_SECTION(); + return mp_obj_new_int(state); +} +MP_DEFINE_CONST_FUN_OBJ_0(machine_disable_irq_obj, machine_disable_irq); + +STATIC mp_obj_t machine_enable_irq(mp_obj_t state_in) { + uint32_t state = mp_obj_get_int(state_in); + MICROPY_END_ATOMIC_SECTION(state); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(machine_enable_irq_obj, machine_enable_irq); + STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_umachine) }, + { MP_ROM_QSTR(MP_QSTR_unique_id), MP_ROM_PTR(&machine_unique_id_obj) }, + { MP_ROM_QSTR(MP_QSTR_soft_reset), MP_ROM_PTR(&machine_soft_reset_obj) }, { MP_ROM_QSTR(MP_QSTR_reset), MP_ROM_PTR(&machine_reset_obj) }, { MP_ROM_QSTR(MP_QSTR_reset_cause), MP_ROM_PTR(&machine_reset_cause_obj) }, { MP_ROM_QSTR(MP_QSTR_bootloader), MP_ROM_PTR(&machine_bootloader_obj) }, { MP_ROM_QSTR(MP_QSTR_freq), MP_ROM_PTR(&machine_freq_obj) }, + + { MP_ROM_QSTR(MP_QSTR_idle), MP_ROM_PTR(&machine_idle_obj) }, + { MP_ROM_QSTR(MP_QSTR_lightsleep), MP_ROM_PTR(&machine_lightsleep_obj) }, + { MP_ROM_QSTR(MP_QSTR_deepsleep), MP_ROM_PTR(&machine_deepsleep_obj) }, + + { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&machine_disable_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&machine_enable_irq_obj) }, + + { MP_ROM_QSTR(MP_QSTR_time_pulse_us), MP_ROM_PTR(&machine_time_pulse_us_obj) }, + { MP_ROM_QSTR(MP_QSTR_mem8), MP_ROM_PTR(&machine_mem8_obj) }, { MP_ROM_QSTR(MP_QSTR_mem16), MP_ROM_PTR(&machine_mem16_obj) }, { MP_ROM_QSTR(MP_QSTR_mem32), MP_ROM_PTR(&machine_mem32_obj) }, diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index d409ccfd4d..9ba381030b 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -109,6 +109,7 @@ #define MICROPY_PY_USELECT (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_PY_MACHINE_PIN_MAKE_NEW mp_pin_make_new +#define MICROPY_PY_MACHINE_PULSE (1) #define MICROPY_PY_MACHINE_I2C (1) #define MICROPY_PY_MACHINE_SPI (1) #define MICROPY_PY_MACHINE_SPI_MSB (SPI_MSB_FIRST) From 0e4458707673bdf04a812ab9cd2ae06712bcdd1a Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 1 Feb 2021 13:40:28 +1100 Subject: [PATCH 336/337] docs/library/machine.Pin.rst: Make it clear which methods are not core. Signed-off-by: Damien George --- docs/library/machine.Pin.rst | 58 +++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/docs/library/machine.Pin.rst b/docs/library/machine.Pin.rst index 095240fe14..f8e9e1054d 100644 --- a/docs/library/machine.Pin.rst +++ b/docs/library/machine.Pin.rst @@ -33,8 +33,8 @@ Usage Model:: # read and print the pin value print(p2.value()) - # reconfigure pin #0 in input mode - p0.mode(p0.IN) + # reconfigure pin #0 in input mode with a pull down resistor + p0.init(p0.IN, p0.PULL_DOWN) # configure an irq callback p0.irq(lambda p:print(p)) @@ -160,25 +160,6 @@ Methods Set pin to "0" output level. -.. method:: Pin.mode([mode]) - - Get or set the pin mode. - See the constructor documentation for details of the ``mode`` argument. - -.. method:: Pin.pull([pull]) - - Get or set the pin pull state. - See the constructor documentation for details of the ``pull`` argument. - -.. method:: Pin.drive([drive]) - - Get or set the pin drive strength. - See the constructor documentation for details of the ``drive`` argument. - - Not all ports implement this method. - - Availability: WiPy. - .. method:: Pin.irq(handler=None, trigger=(Pin.IRQ_FALLING | Pin.IRQ_RISING), *, priority=1, wake=None, hard=False) Configure an interrupt handler to be called when the trigger source of the @@ -220,6 +201,41 @@ Methods This method returns a callback object. +The following methods are not part of the core Pin API and only implemented on certain ports. + +.. method:: Pin.low() + + Set pin to "0" output level. + + Availability: nrf, rp2, stm32 ports. + +.. method:: Pin.high() + + Set pin to "1" output level. + + Availability: nrf, rp2, stm32 ports. + +.. method:: Pin.mode([mode]) + + Get or set the pin mode. + See the constructor documentation for details of the ``mode`` argument. + + Availability: cc3200, stm32 ports. + +.. method:: Pin.pull([pull]) + + Get or set the pin pull state. + See the constructor documentation for details of the ``pull`` argument. + + Availability: cc3200, stm32 ports. + +.. method:: Pin.drive([drive]) + + Get or set the pin drive strength. + See the constructor documentation for details of the ``drive`` argument. + + Availability: cc3200 port. + Constants --------- From 78b23c3a1f064dc19bbee68930ef3aba110c781c Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 3 Feb 2021 00:59:07 +1100 Subject: [PATCH 337/337] all: Bump version to 1.14. Signed-off-by: Damien George --- docs/conf.py | 2 +- py/mpconfig.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 4ca4d18f50..dd6cc31fb8 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -74,7 +74,7 @@ copyright = '2014-2021, Damien P. George, Paul Sokolovsky, and contributors' # # We don't follow "The short X.Y version" vs "The full version, including alpha/beta/rc tags" # breakdown, so use the same version identifier for both to avoid confusion. -version = release = '1.13' +version = release = '1.14' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/py/mpconfig.h b/py/mpconfig.h index 518bddd6c8..d9a30cd300 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -28,7 +28,7 @@ // Current version of MicroPython #define MICROPY_VERSION_MAJOR 1 -#define MICROPY_VERSION_MINOR 13 +#define MICROPY_VERSION_MINOR 14 #define MICROPY_VERSION_MICRO 0 // Combined version as a 32-bit number for convenience